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:

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/.