Discussing Reference Patterns

For the most part the docs are really well written and I don't get confused. But I did in this case. So I hope to discuss it a bit with others.

Reference Pattern: Definition

In the docs we find:

Reference patterns dereference the pointers that are being matched and, thus, borrow them.

It was hard to read for me. My understanding goes something like:

Reference patterns test that the scrutinee is a reference, and either match its value, or bind it. When binding it, the value is reborrowed, not moved or copied.

PS2: even here, one needs clear examples to easily get it imho.

Reference Pattern: An Example

let b = match int_reference { &0 => "zero", _ => "some" };

If int_reference is a reference i.e & and its value is 0 we get the first arm. Changing 0 by hello it binds it to hello.

But mutable references it will need a reborrow, or they are moved. So when binding it needs to do hello = & *int_reference.

Assuming this is the case, I suspect the explanation is a bit too abridged in the documentation. That's why I added:

When binding it, the value is reborrowed, not moved or copied.

Reference Patterns: Are they Irrefutable ?

Docs say:

Reference patterns are always irrefutable.

But that example from the docs above clearly shows they are refutable! They wouldn't be if we had &x, where a bind happens; but that's not always true.

What's your interpretation?


PS: forgot adding the link, here it is Patterns - The Rust Reference

Unless the referenced data type implements Copy and thus has copy semantics, dereferencing causes a move when bound to a variable. E.g.:

#[derive(Debug)]
struct NotCopy(u8);

fn main() {
    let int_reference = &NotCopy(0);
    let &x = int_reference;

    println!("{x:?}");
}

does not compile because we are trying to move out of int_reference.


The reference pattern is only the & part of the pattern. It is irrefutable because if we try matching any type that is not a reference with a reference pattern, the compiler will throw a type error and every reference is matched by the reference pattern.

2 Likes

By "the value is reborrowed" I meant that & *ref needs to happen; meaning the same that you wrote. i.e so that it isn't moved.

But maybe you meant that this isn't possible and is always moved?

Unsure whether you see it like this as well or maybe not.

The last part I agree, I thought it was the whole thing.

I'm unsure I understand how reborrowing relates to reference patterns in your example. Reference patterns will dereference the reborrow just the same, causing the same problem of a moved value in the example I gave above:

#[derive(Debug)]
struct NotCopy(u8);

fn main() {
    let int_reference = &NotCopy(0);
    let &x = &*int_reference;

    println!("{x:?}");
}

Playground.

You can avoid the move of the referenced value by removing the reference pattern, causing the default binding mode for x to switch to ref, binding x to &NotCopy instead of NotCopy:

#[derive(Debug)]
struct NotCopy(u8);

fn main() {
    let int_reference = &NotCopy(0);
    let x = int_reference;

    println!("{x:?}");
}

Playground.

Maybe you could provide a code snippet with shows what you mean?

That makes sense. Let me back track.

So in the original def

Dereference pointers that are being matched and thus borrows them

The dereference is because the x in your example binds to the *ref. And the & is the pattern, and considering the compiler that never fails, because the hand right side must be a reference as well.

But what is the borrow referring to? To the move or copy?

You mean the emphasised part of the docs, right?

Reference patterns dereference the pointers that are being matched and, thus, borrow them.

I'm not sure why this is stated explicitly, but I'm sure it is referring to the reference itself, not the dereferenced value. Maybe it was meant to say

Reference patterns dereference the pointers that are being matched and, thus, read them.

The Ferrocene spec omits this completely:

Yes that one. It's led me astray that wording, plus some of my incompetence.

I've opened an issue:

1 Like

I hope they respond, it doesn't look like they do very much.

Further on, I find unclear this. Are they missing Range Expressions in that list?

Edit to add a bit more context

With the wording

  • Tuples of assignee expressions.
  • Slices of assignee expressions.
  • Tuple structs of assignee expressions.
  • Structs of assignee expressions (with optionally named fields).

I do agree, the full range expression .. (i.e. the rest pattern) should probably be added as a special case to these four types of assignee expressions, as .. on its own is not an assignee expression (unlike all the other assignee expressions listed, including _), but can be part of the these "destructuring a container / complex data type" assignee expressions to denote multiple unmatched values.

I don't know if it's what the reference was trying to say or not, but

    let mut local = String::new();
    let mut_ref = &mut local;

    // This moves `mut_ref`
    // let x = mut_ref;
    
    // These reborrow `*mut_ref`
    let x: &mut _ = mut_ref;
    let x: &_ = mut_ref;

    // These reborrow `*mut_ref` as well
    let &mut ref mut x = mut_ref;
    let &mut ref x = mut_ref;

    let __ = mut_ref;