What is the purpose of a reference pattern?

In the following code , it seems A and B have same effect. Can I have an example that makes difference ? Many thanks!

struct S(u8);

struct D<'a> {
    id : &'a S,
}

fn main() {
    let x = D{id:&S(6)};
    
    if let D{id:&S(x@1..=10)} = x  {   // A this is the reference pattern
        println!("{}",x);
    }
    
    if let D{id:S(x@1..=10)} = x  {    // B
        println!("{}",x);
    }
}

Since patterns are duals to the expressions they destructure, a reference pattern is needed when you have a reference and you want to dereference it. To put it simply, to first approximation let &value = reference has the same effect as let value = *reference.

Now, newer versions of Rust unfortunately allow references and non-references to be treated identically in patterns in some cases, without the programmer being required to state it (i.e. there is implicit referencing and/or dereferencing going on). This is indeed confusing and I consider it a misfeature, but that's how the language has been changed.

You can observe this through compiler errors: in the first case, the type of x is u8, whereby in the second case, it's &u8: playground.

By the way, I've re-formatted your code in the above Playground according to Rust coding idioms.

3 Likes

So B works because of the automatic dereferencing of the value to match the pattern, correct?

Edit: Oh, no, that's not right.

It's actually dereferencing the value to match the pattern, but then binding x to the reference instead of the value because you haven't "unwrapped" the reference by putting the & in the pattern. That's interesting.

In patterns you have two tools to reference and dereference: ref and &. Given let x = D{id:&S(6)}; , the pattern D { id: &S(x) } explicitely removes the &'a borrow in field id of D, as mentioned above and x is u8. You could also write D { id: &S(ref x) } which would explicitly make x a &u8. Because of "match ergonomics", rustc will try to introduce and borrows as needed for the matched value and the used value to "just work". Due to that, if you write D { id: S(x) }, what rustc is doing is exactly the same as D { id: &S(ref x) }: it removes the reference in id, but it adds a reference in x so that the whole thing isn't moved.

This is more useful when you don't want to move a certain binding into all of the match arms, because it lets you write if let D {..} = &x {..} and in the patterns you don't have to think about the borrows too much. The "new" behavior is slightly harder to follow at a glance (because of the implicit hidden dereferencing), but it does reduce verbosity (sometimes very significantly!), it is impossible to introduce performance degradation of your code because of this (rustc will not .clone() your bindings by accident), and if you misuse it, the suggestions that rustc gives you should get you to a working state for the vast majority of cases (if not all).

1 Like

Yes. The second one is equivalent with:

if let D { id: &S(ref x @ 1..=10) } = x  {
    println!("{}", x);
}
1 Like