2

I have read the rfc 2005, knowing the process of manipulation is a repeated operation. And I say encounters reference pattern, I am not talking about encountering reference pattern at the first iteration like the following one:

let x = &String;
// The binding mode is `move`, so x is of type `&String`

But some cases where binding mode is shifted to ref/ref mut during the previous non-reference pattern iteration, and then encounters a reference pattern.

I have read the rfc very carefully, and I found this sentence The *default binding mode* only changes when matching a reference with a non-reference pattern., does this mean reference pattern will do nothing to the matching process, but someone told me reference pattern will reset the binding mode back to the default move(see case 2).

There are two cases where the "correct" inferences seem conflict with each other:

Correct means that the inferred type is the same as the one inferred by the compiler, but I don't know if the inferring process is also correct, which is the reason why I post this question.

// case 1
let (a, b) = &(&String, &String); // a and b are of type &&String
// case 2
let (&a, &b) = &(&String, &String); // a and b are of type String

Inference for case 1:

First, we are matching &(&String, &String) against (a, b), and (a, b) is non-reference pattern, so we deref &(&String, &String) and set binding mode from move(default one) to ref. Then we are matching (&String, &String) against (a, b) , with ref binding mode, a and b are of type &&String.

Why did it stop at matching (&String, &String) against (a, b), shouldn't we continue to match &String against a and b respectively?

If we continue, matching &String against a, a is reference pattern, what should we do now?

Inference for case 2:

First, we are matching &(&String, &String) against (&a, &b), (&a, &b) is non-reference pattern, so we deref &(&String, &String) and set binding mode to ref. Then we match (&String, &String) against (&a, &b), which is basically matching &String against &a, &a is reference pattern, so we reset binding mode back to move, making a of type String.

Contradiction:

In case 2, when encounters reference pattern, what we do is to reset binding mode to the default move. However in case 1(if we didn't stop at that point, keeping matching &String against a), a is also reference pattern, if we still reset binding mode to move, then a will have type &String instead of &&String.

And there is another question, which is when should this inferring algorithm stop? In case 1, if the algorithm should stop at that point, then everything makes senses.

John Kugelman
  • 349,597
  • 67
  • 533
  • 578
Steve Lau
  • 658
  • 7
  • 13
  • This is not duplicate with [1](https://stackoverflow.com/questions/71343407/match-ergonomics-and-pattern),[2](https://stackoverflow.com/questions/71731788/weird-type-when-pattern-matching-references), [3](https://stackoverflow.com/questions/70641939/what-does-pattern-matching-a-non-reference-against-a-reference-do-in-rust) and [4](https://stackoverflow.com/questions/55625001/why-does-pattern-matching-on-optiont-yield-something-of-type-somet) – Steve Lau Apr 20 '22 at 11:29
  • rust is strongly typed, so you can just forget about this and just listen what compiler said when it doesn't compile. – Stargateur Apr 20 '22 at 11:40
  • @Stargateur. Thanks for your comment:) Yep, rust's static checking can guarantee everything in right place, but I still wanna know what is going on under the hood:) – Steve Lau Apr 20 '22 at 11:44
  • Why are you assuming the binding mode ever resets to `move` when the RFC literally states "Note that there is no exit from the ref binding mode.", and the state machine shows no transition back to move? – Masklinn Apr 20 '22 at 12:11
  • "Why did it stop at matching `(&String, &String)` against `(a, b)`, shouldn't we continue to match `&String` against a and b respectively?" because it's trivially uninteresting: a binding pattern is a reference pattern *and* always succeeds. `&&String` is matched against `a`, this succeeds, this always succeeded, match ergonomics does not impact it, any time spent on it is wasted. – Masklinn Apr 20 '22 at 12:16
  • @Masklinn, Thanks for your comment. I originally posted this [question](https://stackoverflow.com/questions/71731788/weird-type-when-pattern-matching-references), and the [answer](https://stackoverflow.com/a/71732428/14092446) said so. Maybe I misunderstood the answer? I have seen that sentence and states machine illustration from the RFC, but seems like I didn't get what that mean – Steve Lau Apr 20 '22 at 12:20
  • @Masklinn, would you like to post an answer explicitly describing this inference process/algorithm? What will happen on each case? When is the time to stop? I am still kind of confused... Sorry about this:( – Steve Lau Apr 20 '22 at 12:33
  • 2
    The big question for me is why matching `let (&a, &b) = &(&String, &String);` results with `a` being of type `String`. I would expect it to be `&String`. It is as if matching a reference-pattern (`&a`) the default binding mode is set back to `move`. But this doesn't seem to be documented anywhere. – rodrigo Apr 20 '22 at 15:18
  • @rodrigo, yep, I posted this [question](https://stackoverflow.com/questions/71731788/weird-type-when-pattern-matching-references) to just to figure it out. After reading the answer of that post, I thought I have understood it. Today I review my old question, finding that I am still confused... – Steve Lau Apr 20 '22 at 15:41
  • 1
    I've found some discussion about this [here](https://github.com/rust-lang/rust/issues/42640#issuecomment-386368289). It looks like this is what was implemented, but it was never documented or even formally agreed. – rodrigo Apr 20 '22 at 15:51
  • I just searched rust forum, some folk said "It's [inconsistent (broken)](https://github.com/rust-lang/rust/issues/64586#issuecomment-599823971) to boot. Also, reader beware, the [implementation does not match the RFC](https://github.com/rust-lang/rust/issues/42640#issuecomment-389685325) in other ways too." – Steve Lau Apr 21 '22 at 03:06

1 Answers1

1

Seems that I have found the answer. There are several things to clarify:

  1. match ergonomics only covers the cases where we are matching non-reference pattern against reference, so the title of this question is wrong, match ergonomics does nothing when encounters reference pattern. The algorithm of match ergonomics is just something like:

    fn match_ergonomics() {
        if (the reference we are matching is `&xx`) {
            binding_mode = `ref`
        } else if (the reference we are matching is `&mut xx`) {
            if (binding_mode == `ref`) {
                binding_mode = `ref`;
            } else {
                binding_mode = `ref mut`;
            }
        }
    }
    
  2. default binding mode is a concept introduced for match ergonomics, but this is not exclusive to the cases covered by match ergonomics, it has been integrated into the whole rust matching system.

  3. The concept of reference pattern can be confusing. In rfc:

    A reference pattern is any pattern which can match a reference without coercion. Reference patterns include bindings, wildcards (_), consts of reference types, and patterns beginning with & or &mut.

    While in rust reference, it is just pattern beginning with &/&mut.

  4. Matching is a recursive process, for every procedure in this recursion, there are several cases depending what pattern we are encountering, which include the match ergonomics cases.

  5. There are actually two situations where we will change the DBD, one is matching non-reference pattern against reference, and the other is matching &[mut] pat against reference.

    matching against change to DBM
    non-ref pattern reference set to ref or ref mut
    &[mut] pat reference set to move

Matching algorithm

// The terminologies we are using in this algorithm come from rust reference
If no `default binding mode` is set, set the `default binding mode` to `move`.

If the pattern is an identifier pattern:
    If we have explicit `ref`:
        bind by reference;
    else if we have explicit `ref mut`:
        bind by mutable reference;
    else:
        // When explicit `ref/ref mut` are absent
        // default binding mode decides how to bind
        If the binding mode is `move`, bind directly.
        If the binding mode is `ref`, bind by immutable reference.
        If the binding mode is `ret mut`, bind by mutable reference.
    
    If the pattern has an inner pattern, repeat this process with that pattern.

else if the pattern is a `&[mut]` reference pattern:
    // matching `&[mut]` against non-reference triggers
    // a compiler error
    Ensure that we are matching against a `&[mut]` reference.

    Dereference the scrutinee expression.
    Set the binding mode to `move`.  
    Repeat this process with the inner pattern.

else if the pattern is any kind of destructuring pattern:
    Set `T` to the type implied by the pattern.
    Ensure that we are matching against a `T` value or `&[mut] T` reference.
    
    // cases covered by match ergonomics
    If we are matching against a `&T` reference:
        Dereference the scrutinee expression.
        Set the binding mode to `ref`.
    else if we are matching against a `&mut T` reference:
        Dereference the scrutinee expression.
        If the binding mode is `move`, set the binding mode to `ref mut`.


    else
        destructure the value;
    Repeat this process for all fields in the pattern.

Inference for case 1 and 2

// case 1
let (a, b) = &(&String, &String); // a and b are of type &&String

First procedure: we are matching (a, b) against &(&String, &String), which goes into case 3(any other kind of destructuring pattern). &(&String, &String) is a &T, so deref it and set default binding mode to ref.

Second procedure: we are matching (a, b) against (&String, &String), which is basicially matching a against &String. a is a identifier pattern, which goes into case 1. We don't have explicit ref/ref mut and default binding mode is ref, so bind by immutable ref, making a of type &&String.

All the patterns are matched, recursion is over.

// case 2
let (&a, &b) = &(&String, &String); // a and b are of type String

First procedure: we are matching (&a, &b) against &(&String, &String), which goes into case 3. Deref and set default binding mode to ref

Second procedure: we are matching (&a, &b) against (&String, &String), which is basically matching &a against &String. &String is a reference pattern, so we goes into case 2, and set default binding mode to move, &a = &String, so a has type String

All the patterns are matched, recursion is over.

The algorithm may not be that accurate, welcome people who know this stuff to edit this answer.

Steve Lau
  • 658
  • 7
  • 13