rfcs icon indicating copy to clipboard operation
rfcs copied to clipboard

Extend `&pattern` to general `Deref` implementation.

Open ticki opened this issue 7 years ago • 17 comments

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.

ticki avatar Aug 07 '17 20:08 ticki

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.

ExpHP avatar Aug 07 '17 20:08 ExpHP

@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.

ticki avatar Aug 08 '17 09:08 ticki

Is this the same as box patterns, in the case of Box<T>? https://github.com/rust-lang/rust/issues/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 avatar Aug 08 '17 17:08 SimonSapin

See also https://github.com/rust-lang/rfcs/blob/master/text/0469-feature-gate-box-patterns.md and https://github.com/rust-lang/rfcs/pull/462.

SimonSapin avatar Aug 08 '17 17:08 SimonSapin

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'
}

ExpHP avatar Aug 08 '17 20:08 ExpHP

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 avatar Aug 08 '17 21:08 SimonSapin

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

SimonSapin avatar Aug 08 '17 21:08 SimonSapin

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.

ExpHP avatar Aug 08 '17 21:08 ExpHP

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.

boomshroom avatar Aug 13 '17 04:08 boomshroom

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.

Techcable avatar Oct 05 '17 05:10 Techcable

It looks like this issue was initially suggested by @nikomatsakis in https://github.com/rust-lang/rfcs/pull/2005#issuecomment-303152861 . 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?

deontologician avatar Jan 26 '18 20:01 deontologician

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 avatar Mar 16 '19 18:03 Nadrieril

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 https://github.com/rust-lang/rfcs/pull/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)

Nadrieril avatar Mar 16 '19 21:03 Nadrieril

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.

boomshroom avatar Mar 16 '19 21:03 boomshroom

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(_) => ()
    }
}

roblabla avatar Apr 01 '19 16:04 roblabla

Need to match deep strings so badly...

AlbertMarashi avatar Feb 14 '22 07:02 AlbertMarashi

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

safinaskar avatar Aug 03 '22 16:08 safinaskar