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
againsta
andb
respectively?If we continue, matching
&String
againsta
,a
isreference 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.
Seems that I have found the answer. There are several things to clarify:
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`;
}
}
}
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.
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 (
_
),const
s of reference types, and patterns beginning with&
or&mut
.
While in rust reference, it is just pattern beginning with &/&mut
.
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.
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 |
// 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.
// 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.