let &x = &1
refkeyword: what's the difference between using
Some(ref x) =>and
Some(&x) =>in patterns? Maybe no difference?
box (x, y) =>in patterns
matchis not the only place where pattern matching occurs. It also happens in many other places:
for, function arguments, etc. Even a simple
let x = 1exhibits pattern matching: we match 1 against pattern
x. This is called the identifier pattern and it never fails, hence it is called a irrefutable pattern. We must use irrefutable patterns with function parameters,
forloops. If we try to write something like
let Some(x) = Some(1), it will fail to compile since the match could fail and we aren't covering all the cases. A pattern which may fail is called a refutable pattern. I am reminding you about those
letpatterns here just because it's easier to use them in examples rather than in a fully fledged
matcheven though such examples may feel more contrived.
&'s like this:
let &y = &1. After that we can use
yand its value would be 1, no surprises:
std::any::type_name::<T>()function from stable Rust. This is useful for investigating pattern matching, auto-dereferencing, and other situations when the compiler may do something strange and the type might not be clear from context:
xs.iter().map(|&x| x + 1).collect(). Note how we use the
&xpattern here to dereference (deconstruct) the pointer returned by the iterator. We can also use
map(|x| *x + 1)for that, making dereference explicit, but I prefer the first variant since one can immediately see from the
&xpattern that a parameter is a reference without looking at its usage. I've run a quick
grepover Rust standard library looking for iter/map combinations and it feels like the first variant is more popular when we need to dereference, but it's probably a matter of taste.
let &x = 1?
let x = y, we have several options with respect to binding modes.
yis moved into
xand we can't use it again. If
Copytrait then we copy. Those two binding modes are normally called binding by value. There is also binding by reference, which is introduced with
refkeyword attached to an identifier pattern like this:
let ref x = y. This means that we want to borrow
yinstead of moving/copying it, so
xwill be a reference. It is exactly the same as
let x = &y. For mutable reference there is
ref mut, i.e.,
let ref mut x = ywhich is the same as
let x = &mut y.
refis just a funky way of saying that you want to borrow, why is it needed? Is just using
refbecomes more useful when used in nested patterns.
refin Rust code, I thought that it was an antiquated way of borrowing, a remnant from previous Rust versions. It is indeed so, mostly because of the feature called "match ergonomics" which we look into in the next section. This feature mostly obsoleted the need for
refand so in modern Rust using
refis rarely needed, but no doubt you'll see it a lot in existing codebases. I think it's easy to understand it better by contrasting with other patterns. Let's say we pattern match on an Option with
Some(ref y) =>and
Some(&y) =>in patterns is that the first borrows whatever is in
Someand the second dereferences the pointer in Some (remember that we've already covered reference patterns in previous section).
Some(ref y) =>and some
Some(y) =>is that in the first case we borrow and in the second case we move/copy into
refbecause: a) you needed to match references with reference patterns b) you had to use
refbecause you couldn't move part of Option into
s, since you only had a borrowed version of Option:
reffor the same reasons as in (1).
refand reference patterns:
xis a reference which is matched by non-reference patterns. Therefore it automatically dereferences
xand uses appropriate binding modes for identifiers in that pattern, in this case "bind by reference". In other words, it automatically inserts
reffor you, since it's the only sensible thing to do in this situation.
Match expressions are an area where programmers often end up playing 'type Tetris': adding operators until the compiler stops complaining, without understanding the underlying issues. This serves little benefit - we can make match expressions much more ergonomic without sacrificing safety or readability.
s.children? It is a little bit non-obvious, since
sis a reference and the we have a field accessor. Knowing that Rust likes to do things automatically for us, we may suspect that something "auto-" is happening here. Indeed, here we have an example of auto-dereferencing. If the left hand side of
.operator is a pointer, it's dereferenced as many times as needed to get access to the field, as described here. So basically
s.childrenis equivalent to
(*s).children. And therefore its type is
Tand can't move out of it (and don't want to) we need to match a reference to
s.children(as opposed to
s.childrenitself) to kick-in the match ergonomics process. Note that we don't need
chwhen it is bound? Remember that we bind by reference here, so it is
&Box<(T, T)>. You can easily check it with
print_type!macro listed above.
Box). Auto-dereferencing to the rescue! We can just say
ch.0and this will add ** automatically. Finally, we need to borrow that with
&: this operator has lower precedence than field accessors, so
&a.bis identical to
&(a.b)In order to appreciate how much "auto-" is happening here let's look at the fully parenthesised unambiguous analog of
&ch.0(it even broke my Markdown renderer!):
(left, right). How do we do this?
right. But not so fast: unfortunately, match ergonomics only works for ordinary references and we have a
Boxthere! So, we have two options: either use
box_patternsfeature from unstable Rust or wait for match ergonomics starting to work for anything implementing
Derefincluding Boxes as discussed here.
box_patternslooks like this: