Click here to expand some in-depth explanation/examples of what kind of quirks I’m referring to!
First example, reborrowing:
fn foo(r: &mut i32) -> &mut i32 {r}
let mut x = 0;
let x_ref = &mut x;
let r = foo(x_ref);
// *x_ref += 1; // illegal here
*r += 1;
*x_ref += 1;
in order to make more programs compile, Rust ensures that passing x_ref
to a function like that does not just move the mutable reference into foo
(note how the last line can still access x_ref
). But also, the call to foo
is not just implicitly ordinarily borrowing x_ref
. (If it was immutably borrowing x_ref
then the resulting & &mut i32
could not be used to get back a &mut i32
again, but mutably borrowing x_ref
doesn’t work either because it isn’t a mut
variable.) Instead what’s happening here is that x_ref
is re-borrowed. Note the error message if the 5th line is uncommented:
error[E0503]: cannot use `*x_ref` because it was mutably borrowed
--> src/main.rs:7:1
|
6 | let r = foo(x_ref);
| ----- borrow of `*x_ref` occurs here
7 | *x_ref += 1; // illegal here
| ^^^^^^^^^^^ use of borrowed `*x_ref`
8 | *r += 1;
| ------- borrow later used here
It only talks about borrowing *x_ref
and ignores the question of what actually happened to the x_ref
reference. To understand this, one should note that in fact the call foo(x_ref)
is equivalent to foo(&mut *x_ref)
.
Second example, mutable references inside structs vs enums
This compiles
let mut x = 0;
let y = (&mut x, 0);
*y.0 += 1;
*y.0 += 1; // do this twice to make sure that `y.0` was not moved
as does this
struct S<A> {
field1: T<A>,
some_more_fields: u32,
}
struct T<A> {
a: A,
blah: bool,
}
let mut x = 0;
let y = S {
some_more_fields: 42,
field1: T {
a: &mut x,
blah: false,
}
};
*y.field1.a += 1;
*y.field1.a += 1; // do this twice to make sure that `y.0` was not moved
but try modifying x
through y
in this situation
let mut x = 0;
let y = Some(&mut x);
You’ll find that match
doesn’t easily allow you the same kind of direct access to fields as .field
notation gives you (and direct field access isn’t possible for enums). Let’s try a few approaches to modify x
through y
without moving out of y
or changing y
to be mut
.
let mut x = 0;
let y = Some(&mut x);
match y { // moves out of y
Some(x_ref) => *x_ref += 1,
_ => {}
}
/* so a second time won’t work
match y { // moves out of y
Some(x_ref) => *x_ref += 1,
_ => {}
}
*/
let mut x = 0;
let mut y = Some(&mut x);
match &mut y { // obviously needs `mut y`
Some(x_ref) => **x_ref += 1,
_ => {}
}
match y { // works a second time now
Some(x_ref) => *x_ref += 1,
_ => {}
}
let mut x = 0;
let y = Some(&mut x);
match y {
Some(&mut x_ref) => x_ref += 1,
// doesn’t work but suggests to write `mut x_ref`
_ => {}
}
// compiles but doesn’t actually modify x
// (there’s some useful warnings that make this obvious even without the print)
let mut x = 0;
let y = Some(&mut x);
match y {
Some(&mut mut x_ref) => x_ref += 1,
_ => {}
}
println!("{}", x); // prints 0
so, this took me a while to come up with but it does work
let mut x = 0;
let y = Some(&mut x);
match y {
Some(&mut ref mut x_ref) => *x_ref += 1,
_ => {}
}
match y {
Some(&mut ref mut x_ref) => *x_ref += 1,
_ => {}
}
println!("{}", x); // prints 2
Third example, closures and unique immutable borrows:
This compiles / works
let mut x = 0;
let mut y = 0;
let z = (&mut x, &mut y);
println!("{}", std::mem::size_of_val(&z)); // prints 16 on Rust playground
let mut closure1 = || {
*z.0 += 1;
*z.1 -= 1;
};
println!("{}", std::mem::size_of_val(&closure1)); // prints 8 on Rust playground
// so the closure only contains reference to `z`
closure1();
let mut closure2 = || {
*z.0 += 1;
*z.1 -= 1;
};
closure2();
println!("{}, {}", x, y); // prints 2, -2
which is a bit weird since closure1
is apparently capturing z
by reference but through an immutable reference to z
, x
and y
could not be modified, and a mutable borrow is not possible.
We can learn more by trying something illegal
// does not compile
let mut x = 0;
let mut y = 0;
let z = (&mut x, &mut y);
let mut closure1 = || {
*z.0 += 1;
*z.1 -= 1;
};
let mut closure2 = || {
*z.0 += 1;
*z.1 -= 1;
};
closure1(); // illegal
closure2();
Error:
error[E0524]: two closures require unique access to `z` at the same time
--> src/main.rs:12:20
|
7 | let mut closure1 = || {
| -- first closure is constructed here
8 | *z.0 += 1;
| - first borrow occurs due to use of `z` in closure
...
12 | let mut closure2 = || {
| ^^ second closure is constructed here
13 | *z.0 += 1;
| - second borrow occurs due to use of `z` in closure
...
17 | closure1(); // illegal
| -------- first borrow later used here
Note how this error message is different from the error when you try to mutably borrow through closures, e.g.
// does not compile
let mut x = 0;
let mut closure1 = || {
x += 1;
};
let mut closure2 = || {
x += 1;
};
closure1(); // illegal
closure2();
error[E0499]: cannot borrow `x` as mutable more than once at a time
--> src/main.rs:9:20
|
5 | let mut closure1 = || {
| -- first mutable borrow occurs here
6 | x += 1;
| - first borrow occurs due to use of `x` in closure
...
9 | let mut closure2 = || {
| ^^ second mutable borrow occurs here
10 | x += 1;
| - second borrow occurs due to use of `x` in closure
...
13 | closure1(); // illegal
| -------- first borrow later used here
What’s happening here is a special kind of borrow called unique immutable borrow that only happens like this in closures.