10

Why does the compiler reject this code:

struct S<'a> {
    i: i32,
    r: &'a i32,
}

fn main() {
    let mut s = S{i: 0, r: &0};
    {
        let m1 = &mut s;
        m1.r = &m1.i;
    }
    let m2 = &mut s;
}

The error is: "cannot borrow s as mutable more than once at a time" (first borrow: m1, second borrow: m2).

Why is the first borrow of s still alive after m1 goes out of scope?

I read about borrow scope extension beyond the scope of the original borrower. However, this always seemed to involve another borrower outside the scope of the original borrower that "took over" the original borrow, e.g. this code fails with the exact same error, which is clear to me:

fn main() {
    let mut s = 0;
    let r: &mut i32;
    {
        let m1 = &mut s;
        r = m1;
    }
    let m2 = &mut s;
}

In the first example, if I replace m1.r = &m1.i; with m1.r = &dummy; (dummy defined as some &i32) or with let dummy = &m1.i;, the code compiles. The error occurs only if I store a reference to a field in another field of the borrowed struct. I don't see why this should extend the borrow beyond its scope.

My best guess as to what is wrong with the code is:

  • s.r's original lifetime is the whole of main,

  • when I assign a reference to m1.r it has to be that original lifetime, but &m1.i is only valid for as long as m1 lives.

But I might be wrong (the error message would be misleading then).

dacker
  • 457
  • 2
  • 10

1 Answers1

6

First note that

let mut s = S{i: 0, r: &0};
{
    s.r = &s.i;
}
let m2 = &mut s;

Gives

cannot borrow `s` as mutable because `s.i` is also borrowed as immutable

Hopefully this should be clear - if a struct self-borrows then it is borrowed. This points out why any self-borrowing structure is basically useless - it cannot be moved (invalidating its own pointer) nor can and mutable reference be taken to it.


Next one needs to understand that immutable references from mutable references count as borrows into the mutable reference, so extend it. For example

let mut v = ();
let r1 = &(&mut v);
let r2 = &v;

gives

cannot borrow `v` as immutable because it is also borrowed as mutable

It's not clear if this is legally able to be a new borrow into the original structure, but it as-yet does not act as such.

Veedrac
  • 58,273
  • 15
  • 112
  • 169
  • @Levans, thx for the answer, this is clear to me. The interesting part is really the nested scope: in my example the borrower m1 goes out of scope, in your example, the "self"-borrower remains in scope, despite the curly braces. This makes the error clear to me only in your case. – dacker Aug 22 '15 at 11:29
  • "The interesting part is really the nested scope: in my example the borrower m1 goes out of scope" → Why do you assume the **borrow**'s scope is the same as that of the **borrower**'s? The only important aspect of the borrower's scope is that it must be no larger than the scope of the borrow, and that any borrow *of it* (eg. `&mut &mut T`) is restricted by it. The lifetime of the borrow it holds is limitless - consider `let x: &'static str = "foo"`. – Veedrac Aug 22 '15 at 16:27
  • Why do you assume that I assume that? I tried to be explicit by using the term borrow scope instead of lifetime, as explained e.g. [here](http://arthurtw.github.io/2014/11/30/rust-borrow-lifetimes.html). Of course borrow scopes can be larger than the borrower's scope but only if there is another borrower outside the first borrower's scope. – dacker Aug 22 '15 at 21:34
  • But not in this case: `m1` is the only borrower before `m2`. You can see that `m1`'s borrow ends at the closing brace if you comment the line `m1.r = &m1.i;`: the code compiles. Nothing else borrows `s` before `m2`. – dacker Aug 22 '15 at 21:40
  • @dacker "But not in this case: `m1` is the only borrower before `m2`." → No, `m1.r = &m1.i;` results in `s` borrowing from `m1`'s borrow, thus `s` is mutably borrowing from `s`. This will evidently last the lifetime of `s`. – Veedrac Aug 22 '15 at 21:49
  • @dacker I guess the real question is why you expect `m1.r = &m1.i;` not to force an increase in the lifetime of `m1`s borrow. It seems obvious that it must. – Veedrac Aug 22 '15 at 21:52
  • You are right, that is what I expect. All I see is `m1.r` borrowing from `m1.i`, no further borrow from `s`. I was under the impression that the Rust compiler would not do code analysis and so could not "see" that `m1.i` is actually `s.i` (which would be a borrow of `s` as you say). – dacker Aug 22 '15 at 21:59
  • @dacker If it could not see it (or, more accurately, the lifetime of the object you're assigning into), it would not let you do the operation as it could not verify its safety. – Veedrac Aug 22 '15 at 22:04