8

I am learning Rust and I've run into some confusing behaviour. The following code compiles fine and works as expected (edit: added code other than test function, previously omitted):

struct Container<'a> {
    contents : &'a mut i32,
}

fn main() {
    let mut one = Container { contents: &mut 5 };
    test(&mut one);
    println!("Contents: {}",one.contents);
}

fn test<'a>(mut x : &'a mut Container) {
    *x.contents += 1;
    let y = x;
    *y.contents += 1;
    x = y;
    println!("{:?}",*x.contents)
}

Now in the statement

let y = x;

the type is inferred. Because x is of type &'a mut Container, I thought that this would be equivalent:

let y: &'a mut Container = x;

But when I do that, the compiler takes issue:

test_3.rs:25:5: 25:10 error: cannot assign to `x` because it is borrowed
test_3.rs:25     x = y;
                 ^~~~~
test_3.rs:23:33: 23:34 note: borrow of `x` occurs here
test_3.rs:23     let y: &'a mut Container = x;

How is x not borrowed by that point in the correctly working example? I tested by omitting the line x = y; from the correctly working version and the compiler said:

test_3.rs:24:13: 24:14 note: `x` moved here because it has type `&mut Container<'_>`, which is moved by default

So I'm getting a move when I don't explicitly define the type but a borrow otherwise. What is going on, how do I get the same behavior as before while explicitly giving the type, and what is causing move behavior in one case but borrow in the other?

Edited with full program

1 Answers1

8

When you do

let y = x;

a move happens. x is emptied, so to speak, and ownership is transferred to y.

When you do either of

let y: &mut _ = x;
let y: &'a mut _ = x;

x is reborrowed to aid matching the lifetimes. This roughly translates to

let y: &mut _ = &mut *x;
let y: &'a mut _ = &mut *x;

This leaves x non-empty, holding an aliased mutable borrow. Assigning to it thus must wait for y to be destroyed. Alternatively, you can pre-move it

let tmp = x;
let y: &'a mut _ = tmp;

I'll admit it's nonobvious behaviour, and it's a shame that you can't borrow the contents of a value without borrowing the whole value.

Veedrac
  • 58,273
  • 15
  • 112
  • 169
  • 1
    *holding an aliased mutable borrow* — I was under the impression that that is distinctly a thing that safe Rust forbids? – Shepmaster Oct 11 '15 at 18:34
  • 3
    Rust doesn't forbid aliased mutable borrows - it forbids using more than one at a time, or overlapping them in non-lexical ways. When you write `&mut x`, `x` is still a valid stack location - it just isn't legal to read or write to it. – Veedrac Oct 11 '15 at 18:47
  • Interesting. I always wondered what exact rules were the compiler uses to determine move-vs-reborrow. Up until now, I assumed reborrowing only happens in function calls where the function takes a `&mut`. But apparently, being more explicit *within* a function also qualifies for a reborrow. – sellibitze Oct 15 '15 at 13:48