Regarding mir (output in the playground), it looks to me as if that mir has seen (at least some minor) optimizations. I’m too lazy to look up compiler flags that one could perhaps run manually to output less optimized mir.
One way to see the difference between move and re-borrow nonetheless is if you put the value into a struct. E.g.
fn f(x: &mut i32) {
let y: (&mut _,) = (x,);
*y.0 += 1;
}
fn g(x: &mut i32) {
let y: (_,) = (x,);
*y.0 += 1;
}
the &mut _
type annotation makes the compile introduce an implicit reborrow. The resulting mir
fn f(_1: &mut i32) -> () {
debug x => _1; // in scope 0 at src/lib.rs:1:6: 1:7
let mut _0: (); // return place in scope 0 at src/lib.rs:1:19: 1:19
let _2: (&mut i32,); // in scope 0 at src/lib.rs:2:9: 2:10
let mut _3: (i32, bool); // in scope 0 at src/lib.rs:3:5: 3:14
let mut _4: &mut i32; // in scope 0 at src/lib.rs:2:9: 2:10
let mut _5: &mut i32; // in scope 0 at src/lib.rs:2:9: 2:10
let mut _6: &mut i32; // in scope 0 at src/lib.rs:2:9: 2:10
scope 1 {
debug y => _2; // in scope 1 at src/lib.rs:2:9: 2:10
}
bb0: {
_2 = (_1,); // scope 0 at src/lib.rs:2:24: 2:28
_4 = deref_copy (_2.0: &mut i32); // scope 1 at src/lib.rs:3:5: 3:14
_3 = CheckedAdd((*_4), const 1_i32); // scope 1 at src/lib.rs:3:5: 3:14
_5 = deref_copy (_2.0: &mut i32); // scope 1 at src/lib.rs:3:5: 3:14
assert(!move (_3.1: bool), "attempt to compute `{} + {}`, which would overflow", (*_5), const 1_i32) -> bb1; // scope 1 at src/lib.rs:3:5: 3:14
}
bb1: {
_6 = deref_copy (_2.0: &mut i32); // scope 1 at src/lib.rs:3:5: 3:14
(*_6) = move (_3.0: i32); // scope 1 at src/lib.rs:3:5: 3:14
return; // scope 0 at src/lib.rs:4:2: 4:2
}
}
fn g(_1: &mut i32) -> () {
debug x => _1; // in scope 0 at src/lib.rs:5:6: 5:7
let mut _0: (); // return place in scope 0 at src/lib.rs:5:19: 5:19
let _2: (&mut i32,); // in scope 0 at src/lib.rs:6:9: 6:10
let mut _3: (i32, bool); // in scope 0 at src/lib.rs:7:5: 7:14
let mut _4: &mut i32; // in scope 0 at src/lib.rs:6:9: 6:10
let mut _5: &mut i32; // in scope 0 at src/lib.rs:6:9: 6:10
let mut _6: &mut i32; // in scope 0 at src/lib.rs:6:9: 6:10
scope 1 {
debug y => _2; // in scope 1 at src/lib.rs:6:9: 6:10
}
bb0: {
_2 = (move _1,); // scope 0 at src/lib.rs:6:19: 6:23
_4 = deref_copy (_2.0: &mut i32); // scope 1 at src/lib.rs:7:5: 7:14
_3 = CheckedAdd((*_4), const 1_i32); // scope 1 at src/lib.rs:7:5: 7:14
_5 = deref_copy (_2.0: &mut i32); // scope 1 at src/lib.rs:7:5: 7:14
assert(!move (_3.1: bool), "attempt to compute `{} + {}`, which would overflow", (*_5), const 1_i32) -> bb1; // scope 1 at src/lib.rs:7:5: 7:14
}
bb1: {
_6 = deref_copy (_2.0: &mut i32); // scope 1 at src/lib.rs:7:5: 7:14
(*_6) = move (_3.0: i32); // scope 1 at src/lib.rs:7:5: 7:14
return; // scope 0 at src/lib.rs:8:2: 8:2
}
}
differs in
_2 = (_1,);
vs
_2 = (move _1,);
one can see even more differences, if the reference is also reborrowed or moved out of some struct:
fn f(x: (&mut i32,)) {
let y: (&mut _,) = (x.0,);
*y.0 += 1;
}
fn g(x: (&mut i32,)) {
let y: (_,) = (x.0,);
*y.0 += 1;
}
fn f(_1: (&mut i32,)) -> () {
debug x => _1; // in scope 0 at src/lib.rs:1:6: 1:7
let mut _0: (); // return place in scope 0 at src/lib.rs:1:22: 1:22
let _2: (&mut i32,); // in scope 0 at src/lib.rs:2:9: 2:10
let mut _3: (i32, bool); // in scope 0 at src/lib.rs:3:5: 3:14
let mut _4: &mut i32; // in scope 0 at src/lib.rs:1:6: 1:7
let mut _5: &mut i32; // in scope 0 at src/lib.rs:2:9: 2:10
let mut _6: &mut i32; // in scope 0 at src/lib.rs:2:9: 2:10
let mut _7: &mut i32; // in scope 0 at src/lib.rs:2:9: 2:10
scope 1 {
debug y => _2; // in scope 1 at src/lib.rs:2:9: 2:10
}
bb0: {
_4 = deref_copy (_1.0: &mut i32); // scope 0 at src/lib.rs:2:25: 2:28
_2 = (_4,); // scope 0 at src/lib.rs:2:24: 2:30
_5 = deref_copy (_2.0: &mut i32); // scope 1 at src/lib.rs:3:5: 3:14
_3 = CheckedAdd((*_5), const 1_i32); // scope 1 at src/lib.rs:3:5: 3:14
_6 = deref_copy (_2.0: &mut i32); // scope 1 at src/lib.rs:3:5: 3:14
assert(!move (_3.1: bool), "attempt to compute `{} + {}`, which would overflow", (*_6), const 1_i32) -> bb1; // scope 1 at src/lib.rs:3:5: 3:14
}
bb1: {
_7 = deref_copy (_2.0: &mut i32); // scope 1 at src/lib.rs:3:5: 3:14
(*_7) = move (_3.0: i32); // scope 1 at src/lib.rs:3:5: 3:14
return; // scope 0 at src/lib.rs:4:2: 4:2
}
}
fn g(_1: (&mut i32,)) -> () {
debug x => _1; // in scope 0 at src/lib.rs:5:6: 5:7
let mut _0: (); // return place in scope 0 at src/lib.rs:5:22: 5:22
let _2: (&mut i32,); // in scope 0 at src/lib.rs:6:9: 6:10
let mut _3: &mut i32; // in scope 0 at src/lib.rs:6:20: 6:23
let mut _4: (i32, bool); // in scope 0 at src/lib.rs:7:5: 7:14
let mut _5: &mut i32; // in scope 0 at src/lib.rs:6:9: 6:10
let mut _6: &mut i32; // in scope 0 at src/lib.rs:6:9: 6:10
let mut _7: &mut i32; // in scope 0 at src/lib.rs:6:9: 6:10
scope 1 {
debug y => _2; // in scope 1 at src/lib.rs:6:9: 6:10
}
bb0: {
_3 = move (_1.0: &mut i32); // scope 0 at src/lib.rs:6:20: 6:23
_2 = (move _3,); // scope 0 at src/lib.rs:6:19: 6:25
_5 = deref_copy (_2.0: &mut i32); // scope 1 at src/lib.rs:7:5: 7:14
_4 = CheckedAdd((*_5), const 1_i32); // scope 1 at src/lib.rs:7:5: 7:14
_6 = deref_copy (_2.0: &mut i32); // scope 1 at src/lib.rs:7:5: 7:14
assert(!move (_4.1: bool), "attempt to compute `{} + {}`, which would overflow", (*_6), const 1_i32) -> bb1; // scope 1 at src/lib.rs:7:5: 7:14
}
bb1: {
_7 = deref_copy (_2.0: &mut i32); // scope 1 at src/lib.rs:7:5: 7:14
(*_7) = move (_4.0: i32); // scope 1 at src/lib.rs:7:5: 7:14
return; // scope 0 at src/lib.rs:8:2: 8:2
}
}
now apparently we have
_4 = deref_copy (_1.0: &mut i32); // scope 0 at src/lib.rs:2:25: 2:28
_2 = (_4,); // scope 0 at src/lib.rs:2:24: 2:30
vs
_3 = move (_1.0: &mut i32); // scope 0 at src/lib.rs:6:20: 6:23
_2 = (move _3,); // scope 0 at src/lib.rs:6:19: 6:25
(the re-numbering of variable names should be irrelevant, so think of _4
and _3
as the same)
I’m not familiar enough with mir to know the meanings of move
or deref_copy
or operations like creating a tuple with or without the “move
”. Also, in Release mode, you’ll gain additional StorageLive
/ StorageDead
annotations that are also slightly different between these examples, for which I’m also not 100% certain what precise meaning they have.