How to pattern match by immutable value, not reference or mutable value?

In a pattern (for instance, in a match expression) is there a way to directly match as an immutable value?

Suppose we have a type struct T(u64) defined, and then we have a function that has a mutable reference to a value, e.g. fn foo(x: &mut T) {...}.

Patterns will automatically bind matched variables with a type based on context.

  • shared (immutable) reference (match x { T(ref x) ...)
  • exclusive (mutable) reference (match x { T(ref mut x) ...)
  • mutable value (match x { T(mut x) ...)

However, I can't figure out a way to match by immutable value. Going with the above examples, you'd try match x { T(x) ... but x will still be captured by mutable reference

Especially when dealing with Copy types it seems common to deal with values like this.

You can use a mutable reference sometimes to achieve it, even when you don't need the variable to be mutable. This can work, but the compiler will warn that it doesn't need to be mutable. But its suggestion of removing the mut keyword is incorrect, because it breaks the code if you follow that recommendation. (I reported a bug here rust-lang/rust#113451)

Example code:

(Rust Playground Link)

fn main() {
    let mut thing = Thing::Item { mass: 32.5 };
    inflate(&mut thing);
    if let Thing::Item { mass } = thing {
        assert_eq!(mass, 65.0);
    } else {
        panic!("expected an Item");
    }

    let mut person = Thing::Person { age: 22 };
    inflate(&mut person);
    if let Thing::Person { age } = person {
        assert_eq!(age, 22);
    } else {
        panic!("expected a Person");
    }
}

#[derive(Copy, Clone, Debug)]
enum Thing {
    Item { mass: f64 },
    Person { age: u32 },
}

fn inflate(thing: &mut Thing) {
    match thing {
        Thing::Item { mut mass } => {  // <-- WARNING: unused_mut
            println!("I have an item of mass {mass} kg");
            set_thing_mass(thing, mass * 2.0);
        }
        Thing::Person { age } => {
            println!("I have a person of age {age} years");
        }
    }
}

fn set_thing_mass(thing: &mut Thing, new_mass: f64) {
    match thing {
        Thing::Item { mass } => {
            println!("set mass on thing changing from {mass} to {new_mass}");
            *mass = new_mass;
        }
        Thing::Person { .. } => println!("Sorry, can't change a person's mass so easily."),
    }
}

The code works, but the compiler warns like this:

warning: variable does not need to be mutable
  --> src/bin/thing.rs:29:23
   |
29 |         Thing::Item { mut mass } => {
   |                       ----^^^^
   |                       |
   |                       help: remove this `mut`
   |
   = note: `#[warn(unused_mut)]` on by default

warning: `rust-mut-warning` (bin "thing") generated 1 warning (run `cargo fix --bin "thing"` to apply 1 suggestion)

Is there a better way?
The only workaround I can figure out is to match by reference and then immediately make a copy using it with a let statement:

    match thing {
        Thing::Item { mass } => {  // <--- mass: &mut f64
            let mass = *mass;  // <--- WORKAROUND to make: mass: f64
            println!("I have an item of mass {mass} kg");
            set_thing_mass(thing, mass * 2.0);
        }

FYI - I searched the forum and the closest thing I found was some related discussion about pattern binding modes in Strange footgun when adding `mut` to binding - #26 by burjui

It's &mut Thing::Item { mass } => ….

Also.

I generally recommend that you avoid "match ergonomics" altogether. You can turn it off by making Clippy warn or error upon the clippy::pattern_type_mismatch lint.

4 Likes

Thanks H2! That's one combination of & and mut position I forgot to try.

A very helpful article you shared as well, a good refresher and some new ways to think about pattern matching in Rust.

The logic is that you usually need to match the shape of the value with the pattern. If you have a mutable reference to the value, then you should match it using a mutable-reference-to-value pattern as well.

2 Likes

I will try to keep in mind the distinction between expressions and patterns.

What would be interesting/awesome is if the IDE with rust-analyzer could syntax-highlight patterns differently from expressions. It would be a subtle hint that the context is different, and we need to look at it "inside-out" as you say.

I wonder if that's been considered before.

1 Like

You can also match on the value behind the reference instead of the reference itself, that would also give you an immutable value:

match *thing {
    Thing::Item { mass } => {
        mass = 10.0;
        ...
error[E0384]: cannot assign twice to immutable variable `mass`
  --> src/main.rs:28:13
   |
27 |         Thing::Item { mass } => {
   |                       ----
   |                       |
   |                       first assignment to `mass`
   |                       help: consider making this binding mutable: `mut mass`
28 |             mass = 10.0;
   |             ^^^^^^^^^^^ cannot assign twice to immutable variable

@H2CO3
IMHO "match ergonomics" is a valuable feature that makes code a bit nicer, and a newbie would still need to get a firm grip on match patterns sooner or later to become proficient in Rust, so I'm against disabling it, as it rarely causes minor inconveniences. But I get your desire for explicitness.

2 Likes

This topic was automatically closed 90 days after the last reply. We invite you to open a new topic if you have further questions or comments.