Error 596 and a `let` pattern

Hi! A new Rustacean here, trying to understand a pattern in a let statement involving self.

The below snippet is (somewhat) representative of the code I'm writing.

It so happens that following the compiler's advice (on line 13) fixes the problem for me, but I was confused about the error message when it said "[self] is not declared as mutable." Until I realised that the type was inferred so I came up with an alternative fix by adding an explicit type to line 9.

So my question is: what is rustc actually doing in these three scenarios? Are the two fixes equivalent, if not why? Why does the fix on line 13 work, I thought the &mut was needed? Why is the type not inferred as a mutable ref (I thought I had clearly expressed that intent on line 9 with just &mut), or is it and am I failing to grasp something?

Sorry for all the questions, I'm really loving the language, but I'm still getting used to it!

#![allow(unused)]

struct A {
    b: usize,
    c: Vec<isize>,
}

impl A {
    fn e(&mut self) {
        let A {
            b,
            c: ref mut d,
        } = &mut self;
        d.push(3);
        self.c.push(4)
    }
}

fn main() {
    let mut a = &mut A {
        b: 1,
        c: vec![2],
    };
    a.e();
    println!("{:?}", a.c);
}

Errors:

   Compiling playground v0.0.1 (/playground)
error[E0596]: cannot borrow `self` as mutable, as it is not declared as mutable
  --> src/lib.rs:15:13
   |
15 |         } = &mut self;
   |             ^^^^^^^^^
   |             |
   |             cannot borrow as mutable
   |             try removing `&mut` here

error: aborting due to previous error

For more information about this error, try `rustc --explain E0596`.
error: Could not compile `playground`.

To learn more, run the command again with --verbose.

just drop the &mut, self is already a unique reference (has type &mut Self), so you don't need to put &mut self to borrow it.

Also, in your main function, don't take references to literals. References are temporary views into some other value, not some permanent structure.

I thought the pattern matching performed a move, hence I thought &mut was needed... is that not the case?

Yes a pattern matching moves by default, by references are special cased so that no move occurs. This was due to the match ergonomics initiative from 2017,

so this works

let x = A { b: 1, c: vec![2] };
let A { b, c } = &mut x;

// these two are just to show the types
let b: &mut usize = b;
let c: &mut Vec<isize> = c;

Also, unique references are automatically reborrowed when they are used, so you can reuse them easily. Whenever they are supposed to move, they are instead reborrowed.

Technically pattern matching is done against a place, and whether it moves depends on the pattern.

Demonstration that a match doesn't need to be a move, even for a non-reference type in rust 2015:

fn main() {
    let mut s = String::from("asdf");
    match s {
        ref mut r => r.push_str("123"),
    }
    dbg!(s);
}

(But please just use match ergonomics instead of ref keywords, in real code.)

2 Likes

Okay, thank you for the insights. I think I'm tripping up on this unique reference concept, is there a chapter in the book I should (re)read or a spot in the 'nomicon? What determines if a reference is unique or not?

So that is not a move because it uses the ref keyword, correct? But in my case no move occurs because of the type of the unique reference, right?

Okay, you're losing me at 'match ergonomics'...

unique reference is &mut T, a shared reference is &T. You will also see them called mutable and immutable references, but it is vastly more helpful to think in terms of uniqueness. You can read about it here
https://limpet.net/mbrubeck/2019/02/07/rust-a-unique-perspective.html

No move occurs because of the ref mut, which creates a unique reference to the string. But we pass in an owned String to the match, not a reference to a String.

Just pass in &T/&mut T when you want to get a shared/unique reference to it's fields, and don't bother with ref and ref mut.

1 Like

Thank you so much, I think that's making sense to me now!

Just one more thing regarding ergonomics/style: if I only really need a unique reference to one field and shared references to the rest (or just copies because they impl the Copy trait), what would be idiomatic Rust? I really like the conciseness of the deconstruction, but I won't use all the fields in the same way, so I've ended up with this:

struct A {
    b: usize,
    c: Vec<isize>,
    d: Vec<isize>,
}

impl A {
    fn e(&mut self) {
        let A {
            b,
            ref c,
            ref mut d,
        } = *self;
        // ...snip...
    }
}

I don't want a reference to b hence the deref on self and I only intend to mutate the last field, does it make you cringe or cause your eye to twitch?

That's fine, normally I wouldn't even bother with the pattern, but I've seen this too.

See "nicer match bindings" in the 1.26 announcement:

1 Like
struct A {
    b: usize,
    c: Vec<isize>,
    d: Vec<isize>,
}

impl A {
    fn e (self: &mut A)
    {
        let &mut A {
            b, // copy
            ref c, // reborrow as shared reference
            ref mut d, // reborrow as unique reference
        } = self;
        // ...snip...
    }
}

Isn't this just a matter of taste?

Yes, but for most people match ergonomics is easier to read.

This topic was automatically closed 90 days after the last reply. New replies are no longer allowed.