Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Extend &pattern to general Deref implementation. #2099

Open
ticki opened this issue Aug 7, 2017 · 17 comments
Open

Extend &pattern to general Deref implementation. #2099

ticki opened this issue Aug 7, 2017 · 17 comments
Labels
T-lang Relevant to the language team, which will review and decide on the RFC.

Comments

@ticki
Copy link
Contributor

ticki commented Aug 7, 2017

For example, suppose X derefs, then &pat takes X and dereferences it. If the dereferenced value matches pattern pat, it matches.

In other words, it extends &pattern from only allowing dereferencing of &T to allowing dereferencing of arbitrary T: Deref.

Alternative is of course to introduce a seperate syntax for it.

@ExpHP
Copy link

ExpHP commented Aug 7, 2017

Use case or motivating example?

Aside from let &x = Box::new(3); I'm having a hard time picturing when it would even be possible to use this; Most deref coercions I see in daily use have unsized target types, making them ineligible for this.

@ticki
Copy link
Contributor Author

ticki commented Aug 8, 2017

@ExpHP just today, I had the case where I had a RAII type derefing to an enum, which I wanted to match against. The code I ended up with was a clusterfuck, because I needed to add another ident for matching against the enum, which is really annoying. If I could just however use & it would mean, that I could just use one big match, which is significantly simpler and nicer.

@SimonSapin
Copy link
Contributor

Is this the same as box patterns, in the case of Box<T>? rust-lang/rust#29641

This would make pattern matching be able to run arbitrary code, which wasn’t the case before. (For example, struct matching is structural and does not use PartialEq.) I don’t know if this is a reason not to do this, but it should be considered.

@SimonSapin
Copy link
Contributor

@ExpHP
Copy link

ExpHP commented Aug 8, 2017

This would make pattern matching be able to run arbitrary code, which wasn’t the case before.

I'm not certain I see the difference from what's possible today. (Playground)

struct BigRedButton;

impl ::std::ops::Deref for BigRedButton {
    type Target = str;
    fn deref(&self) -> &Self::Target {
        panic!("launching nukes");
    }
}

fn main() {
    // Nothing to see here, just a pattern match.
    let _:&str = &BigRedButton;  // thread 'main' panicked at 'launching nukes'
}

@SimonSapin
Copy link
Contributor

That’s deref coercion happening on the right-hand-side of the assignment, not pattern matching. But yeah, maybe deref patterns are no big deal.

@SimonSapin
Copy link
Contributor

Still, because side effects are be involved we’d need to define the order of pattern "evaluation".

@ExpHP
Copy link

ExpHP commented Aug 8, 2017

Apologies for the noise; after much faffing about I finally see what the difference is.

My mental model of when deref coercions are applied was a bit "off." Apparently they apply to every expression, recursively (AST-wise), but only on the outermost type.

fn main() {
    let red = BigRedButton;
    let borrow: &BigRedButton = &red;
    let _:&str = borrow; // compiles
    let _:Option<&str> = Some(borrow); // compiles
    let _:(&str, &str) = (borrow, borrow); // compiles
    
    let tup: (&BigRedButton,) = (borrow,);
    let _: (&str,) = (tup.0,); // compiles
    
    let _: (&str,) = tup; // AHAH! Finally, a type error.
}

One could say that the examples that compile are exactly those examples where it is possible to insert "as &str" somewhere on the RHS to make the coercions explicit.

@boomshroom
Copy link

Pattern matching on recursive enums.

Currently, this is impossible without #![feature(box_patterns)] or recursive matches. Recursive matches are just ugly as heck and it's going to be deferenced anyway. #![feature(box_patterns)] locks your code behind a nightly feature gate and doesn't even work for things other than Boxes. What if you wanted to pattern match on Strings or Rcs? The file I have open right now has three nested matches: one on the enum, one on the boxed value, and another on the String contained in the boxed value. That was before I decided to use box_patterns anyway.

@Techcable
Copy link

You could add a unsafe DerefTrusted trait to force only 'trusted' deref implementations to run that don't panic or have side effects. Most smart pointers already require unsafe code anyways, and that's the primary use case for these situations (Box, Rc, String). However it would help ensure that both safe and unsafe code isn't unexpectedly broken by matching bad deref implementations.

However, I still feel that that restriction is unessicarry, since we already allow arbitrary code to run in the general case. Personally, I'm often anoyed by the lack of a general solution and the fact that box is given special treatment.

@Centril Centril added the T-lang Relevant to the language team, which will review and decide on the RFC. label Dec 6, 2017
@deontologician
Copy link

It looks like this issue was initially suggested by @nikomatsakis in #2005 (comment) . Some more discussion occurred around this comment (it's a little hard to follow because the thread is intermingled with discussion of a move pattern keyword that's unrelated).

Is there any momentum here? What needs to happen next, a formal RFC written up?

@Nadrieril
Copy link
Member

match_default_bindings is now stabilized. I've looked around and it seems that here is where discussion has moved, but it doesn't look like there is much momentum.

@Nadrieril
Copy link
Member

Quick summary of where we are at:
Motivating example: as @boomshroom mentions, matching on recursive enums.
We would typically want this to compile:

let x: Option<Rc<Option<usize>>> = Some(Rc::new(Some(4)));
match &x {
    Some(Some(_)) => {},
    _ => {},
}

Currently, this works with plain references:

let x: Option<&Option<usize>> = Some(&Some(4));
match &x {
    Some(Some(_)) => {},
    _ => {},
}

And with Boxes, with the box_patterns feature and special syntax:

let x: Option<Box<Option<usize>>> = Some(Box::new(Some(4)));
match &x {
    Some(box Some(_)) => {},
    _ => {},
}

The idea would be to extend the current behaviour of match_default_bindings, to something like #1944. Currently, matching automatically derefs plain references as needed. We would like to extend this behaviour to all types implementing Deref. Another way to phrase this would be to allow &pattern patterns to match values behind any smart pointer and not just plain references. This would effectively obsolete the box_patterns feature. Another option would be to use special syntax like box_patterns, but still extend it to all Deref types.

This feature has been mentioned in the discussion around match_default_bindings, but rapidly put to the side. It also has been discussed in the match ergonomics RFC, but a lot has changed since then. Now that match_default_bindings is stabilized, we'd like to reopen discussion.

The main objections seem to be:

  • it would run abritrary code when matching
  • it could be too magic and thus confusing and/or lead to subtle bugs (this would be less of a problem with special syntax)

@boomshroom
Copy link

I think that when I made the comment, I was thinking of matching on something like

enum Expr {
    App(Box<Expr>, Box<Expr>),
    ...
}

but you example works just as well. One note about deprecating box_patterns is that Box is still special in that it's the only type capable of moving out of a dereference, so match e { Foo(box x) => x } wouldn't work without box_patterns unless x's type is Copy.

@roblabla
Copy link

roblabla commented Apr 1, 2019

Matching a deep String is another use-case:

enum X {
    A(String),
    // ...
}

fn main() {
    match X::A("test".to_string()) {
        X::A(&"test") => (),
        X::A(_) => ()
    }
}

@AlbertMarashi

This comment was marked as spam.

@safinaskar
Copy link

@AlbertMarashi and everybody interested! I implemented deref patterns (including strings) in proc macro crate: https://crates.io/crates/match_deref

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
T-lang Relevant to the language team, which will review and decide on the RFC.
Projects
None yet
Development

No branches or pull requests