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