https://www.catmonad.xyz/blog/nibbles_02.html
Nibbles of Rust
Restructuring Patterns
In Rust, there is this feature known as "pattern matching", whereby
you can take apart a piece of structured data by writing out patterns
which bind to parts of it. This process is known as "destructuring",
because you're breaking a structure into parts.
But there's this old feature which I've unilaterally decided to call
"restructuring patterns":
let x = Some(10);
match x {
Some(ref inner) => {
// Look at the type!
let _: &i32 = inner;
},
None => unreachable!(),
}
See that ref keyword? It adds structure to the value inner when
taking it out of x. Instead of moving an i32 out of x, we're taking a
reference to inside it!
Two keywords are valid in that position: ref and mut, and they do
different things. mut marks a binding as mutable, and can be used in
combination with ref, as ref mut, to introduce a &mut to the
structure of the binding they're next to instead of a &.
This is distinct from using & and &mut themselves in a pattern, as
these do the normal work of destructuring, working as dereference
operators in that position:
let x = Some(&10);
match x {
Some(&inner) => {
// Look at the type!
let _: i32 = inner;
},
None => unreachable!(),
}
Basically, using a type in a pattern lets you rip apart that type,
like &x, &mut x, Json(x), etc., and the super special ref keyword
does the opposite.
Match Ergonomics and Binding Modes
Now, that mental model is good enough that you can pretty much run
with it and it won't steer you wrong. But it's not the whole story.
The modern approach to matching on things with references involved is
covered well by the Match Ergonomics RFC. It defines a notion of
"binding mode", which is the actual thing we're controlling when we
use the ref keyword, and that fact in turn is why we can't use ref
ref inner as a pattern. (Not that I'd ever thought to write that
particular pattern before working on this post.)
So, when you're matching on something, Rust examines the type of the
value being matched on, and decides on a default binding mode from
one of these:
* move, which takes ownership of the value being pattern matched
* ref, which takes a shared borrow from the value being matched
* ref mut, which takes a unique borrow from the value being matched
This is chosen based on whether the type of the value being matched
has an outer layer of &mut, which pushes toward a default binding
mode of ref mut, or a &, which forces the default binding mode to be
ref. If neither reference type is present, the default binding mode
will be move.
This default binding mode is used if, and only if, you don't write a
reference as the outer layer of your pattern. This used to be a
compiler error, but now it's what we basically always do!
With that in mind, the ref keyword is really a tool to change the
binding mode from a default of move to use ref / ref mut instead. In
this model, it really doesn't make sense to nest it in the fashion of
ref ref inner, since it's a switch with only two or three states
where it's used.
But wait...
With all that said, wouldn't it be neat if "restructuring patterns"
were actually a first-class feature? I don't have an idea off the top
of my head what that'd be good for, but the lang-dev in me says we
should investigate. If not for Rust, maybe some hypothetical other
language?
Writing Group
I was motivated to write this post by a writing group we formed in
the RPLCS Discord. Other posts by our group are listed here: https://
www.catmonad.xyz/writing_group/.