I recently tried to figure out how to use the reference inside of an Option<&mut T>
on the stack, without the variable containing the Option
being mut
.
(playground containing all the examples from below)
does_not_compile!{
fn foo() {
let mut x = 1;
let y = Some(&mut x);
if let Some(r) = y {
*r += 1;
}
// error: use of moved value
if let Some(r) = y {
*r += 1;
}
println!("{}", x);
}
}
At first, I thought it might be impossible. This works, but we need to have mut y
:
// workaround using match ergonomics
fn bar() {
let mut x = 1;
// needs mut here now
let mut y = Some(&mut x);
if let Some(r) = &mut y {
// and deref twice
**r += 1;
}
if let Some(r) = &mut y {
**r += 1;
}
println!("{}", x); // -> 3
}
Also the **r
are not very nice. It took me a while until I had everything figured out. IIRC, I also didn’t find any approach that had me getting rid of the **
(even with the mut y
) other than this wordy biest:
// "proper" approach
fn foo() {
let mut x = 1;
let y = Some(&mut x);
// isn’t `&mut ref mut` just ridiculous?
if let Some(&mut ref mut r) = y {
*r += 1;
}
if let Some(&mut ref mut r) = y {
*r += 1;
}
println!("{}", x); // -> 3
}
This makes me wonder: Why don’t we just have a binder to a &mut T
variable re-borrow the reference by default? This would make the first version compile. After all, re-borrows should not make any actual difference in the compiled assembly, right? What would be good reasons against such a change? Of course, taking this approach systematically would mean that even an assignment like let x = y
where y: &mut T
would re-borrow.
Here’s another example of a problem where the compiler tries to insert a move on matching on/binding a &mut T
; this time the Option
is behind a reference:
// similar problems behind a reference
does_not_compile!{
fn baz() {
let mut x = 1;
let y = &mut Some(&mut x);
// error: cannot move out of y.0 which is behind a mutable reference
if let &mut Some(r) = y {
*r += 1;
}
println!("{}", x);
}
}
// solution
fn baz() {
let mut x = 1;
let y = &mut Some(&mut x);
// let’s have *mut* every *mut* second *mut* word *mut* be "mut"!
if let &mut Some(&mut ref mut r) = y {
*r += 1;
}
println!("{}", x); // -> 2
}