Rust: &mut self borrow conflicting with itself.

Created on 3 Feb 2015  ·  20Comments  ·  Source: rust-lang/rust

I don't know why this happens but rovar and XMPPwocky on IRC believed it to be a compiler bug.

struct A {
    a: i32
}

impl A {
    fn one(&mut self) -> &i32{
        self.a = 10;
        &self.a
    }
    fn two(&mut self) -> &i32 {
        loop {
            let k = self.one();
            if *k > 10i32 {
                return k;
            }
        }
    }
}

...gives the following error...

<anon>:12:21: 12:25 error: cannot borrow `*self` as mutable more than once at a time
<anon>:12             let k = self.one();
                              ^~~~
<anon>:12:21: 12:25 note: previous borrow of `*self` occurs here; the mutable borrow prevents subsequent moves, borrows, or modification of `*self` until the borrow ends
<anon>:12             let k = self.one();
                              ^~~~
<anon>:17:6: 17:6 note: previous borrow ends here
<anon>:10     fn two(&mut self) -> &i32 {
...
<anon>:17     }
              ^
error: aborting due to previous error
playpen: application terminated with error code 101

Interestingly, if the second method is changed to...

    fn two(&mut self) -> &i32 {
        loop {
            let k = self.one();
            return k;
        }
    }

This will compile fine.

playpen: http://is.gd/mTkfw5

A-NLL A-borrow-checker A-typesystem C-bug NLL-polonius T-compiler

Most helpful comment

I think I ran into a similar problem in production where an immutable borrow seems to live too long (workaround).

I should also mention that starwed's code no longer compiles on the latest nightly. @oberien suggested a "regression-from-nightly-to-nightly" tag, and it was also suggested I should tag @nikomatsakis in case this was an urgent problem :)

All 20 comments

I believe this is correct behavior. To see why, consider the code with explicit lifetime annotations:

struct A {
    a: i32
}

impl A {
    fn one<'a>(&'a mut self) -> &'a i32{
        self.a = 10;
        &self.a
    }
    fn two<'a>(&'a mut self) -> &'a i32 {
        loop {
            let k = self.one();
            if *k > 10i32 {
                return k;
            }
        }
    }
}

two needs to return a reference with the same lifetime as the mutable borrow given to it. However, since it gets its. This means that k must have lifetime 'a. However, for the returned value of one to have lifetime 'a, the input to one must also have lifetime 'a. This means that Rust is forced to not "reborrow" self and move it in to one. Since mutable references are linear, they can only be moved once, but the loop means that one may need to be called with the same mutable reference repeatedly.

The second example only works because Rust can see that the loop will only run once, so self will only be moved once.

This looks like a non-lexical borrows issue. You can get similar behaviour without any loops at all:

struct Foo { data: Option<i32> }

fn main() {
    let mut x = Foo{data: Some(1)};

    foo(&mut x);
}

fn foo(x: &mut Foo) -> Option<&mut i32> {
    if let Some(y) = x.data.as_mut() {
        return Some(y);
    }

    println!("{:?}", x.data); 
    None
}
<anon>:14:22: 14:28 error: cannot borrow `x.data` as immutable because it is also borrowed as mutable
<anon>:14     println!("{:?}", x.data); 
                               ^~~~~~
note: in expansion of format_args!
<std macros>:2:43: 2:76 note: expansion site
<std macros>:1:1: 2:78 note: in expansion of println!
<anon>:14:5: 14:30 note: expansion site
<anon>:10:22: 10:28 note: previous borrow of `x.data` occurs here; the mutable borrow prevents subsequent moves, borrows, or modification of `x.data` until the borrow ends
<anon>:10     if let Some(y) = x.data.as_mut() {
                               ^~~~~~
<anon>:16:2: 16:2 note: previous borrow ends here
<anon>:9 fn foo(x: &mut Foo) -> Option<&mut i32> {
...
<anon>:16 }
          ^
error: aborting due to previous error

Rustc can't "deal" with conditional borrowing returns.

CC @pcwalton

I think the issue is actually that not the return value's mutability is used but the function argument's mutability.

The following code calls a mutating function that returns an immutable reference. After that I cannot take immutable references.

struct Foo (u8);

impl Foo {
    fn bar(&mut self) -> &u8 {
        self.0 += 1;
        &self.0
    }
}

fn main() {
    let mut x = Foo(42);
    let a = x.bar(); // note: borrow of `x` occurs here
    let b = x.0; // error: cannot use `x.0` because it was mutably borrowed
}

Playpen

I'm still running into this issue on the latest nightly. It appears that it treats returns as having a lifetime until the end of the function, even if the function without the return passes the borrow checker. This seems kind of odd, considering how the point of a return is to end the function early.

If you take a look at @Gankro's example, it passes the borrow checker if you remove the return keyword, even though the resulting function won't work properly.

This is blocked on non-lexical lifetimes, which is in turn blocked on MIR. Ideally this will be fixed by the end of 2016, but a fix won't land soon.

Non lexical borrows feature discussion: https://github.com/rust-lang/rfcs/issues/811

So reading the [internals thread] makes me feel like this isn't fixable (even with NLL). Can someone confirm? (@eddyb?)

cc @rust-lang/lang

This is indeed NLL and, if I'm not mistaken, it would be fixed by the various proposals that I have made. This roughly corresponds to "problem case #3" from my first blog post. The basic idea (in terms of the formulation from the latest blog post) of why it would work is that the subtyping the borrow would only be required to extend until the end of 'a in the branch of the if that causes a return.

@nikomatsakis Could you please elaborate more on how NLL interacts with this issue? In my mental model of &mut references, they can be passed either by move or reborrow, and the problem in this issue is that the return requires the move mode, and the re-use of self afterwards requires reborrow. In my understanding, the new reborrowed &mut reference's lifetime is limited by the lifetime of the variable holding the &mut-reference – in this case, self variable, thus it's limited by the function's body, thus it cannot extend outside the function call. Are the NLL going to change that limitation of reborrows (or maybe there is no such limitation)?

Also I am wondering if fixing this issue is something inherently tied to NLL or is it maybe orthogonal? If it's the latter, maybe it's worth landing a fix before NLL?

Also, if NLL are going to fix this issue, does that mean that under NLL you'll never have to manually choose between a move and reborrow?

Is this going to be fixed any time soon?

@krdln

Could you please elaborate more on how NLL interacts with this issue?

The summary is that, today, if a value gets returned out of the function on any path, the loan must be valid for the rest of the function on all paths. Under the NLL proposal I wrote up, that restriction is lifted.

In more detail, if you take the elaborated signature here, you can see that we must return a reference with lifetime 'a:

fn two<'a>(&'a mut self) -> &'a i32 { .. }

now observe that we call let k = self.one() and then return this k. This means that the type of k must be &'a i32. This in turn means that when we call self.one(), we must supply it a borrow of self with type &'a mut Self. Therefore, at the let k = self.one() we issue a loan of self with lifetime 'a. This lifetime is bigger than the loop (and, indeed, the entire function), so it persists as we go around the loop, leading to the error report. Under the NLL rules that I proposed, the lifetime of k is only required to extend to 'a if the if is taken. Therefore, if the if is not taken, the loan can end earlier.

Does that help?


@osa1

Is this going to be fixed any time soon?

There are still a few steps to go before we can do this, but there is active work on doing the refactorings needed.

@nikomatsakis
Thanks, it helped somewhat. But there's one little thing that I don't understand – what exactly happens when you write self in your code – how the reborrow of self works. I've read your exaplanation of the "Problem case 3" (get_default), where you inline the code inside the caller, but there, you've changed every use of self into borrowing the map variable directly, so that desugaring didn't clear it up for me.

Here's where I'm stuck: When we call let k = self.one(), the self can't be moved (because it's needed later), so it's consider borrowed. Later, we conditionally return that k, so that borrow has to have a lifetime 'a, which outlives the function call. But! We've borrowed from self, which lives only till the end of the function. That limitation seems to shorten 'a, so in my mental model, even under NLL, we should get "does not live long enough" error.

@krdln we've actually borrowed from *self -- that is, we've reborrowed what self refers to. We permit you to borrow *self for a lifetime outliving self itself because the type of self implies a 'lock' for the entire lifetime 'a. Hence, as long as we can guarantee that during 'a, self is no longer used, the result should be an exclusive reference -- in this case, after the fn returns, self has been popped, and hence it could not be used, so just guaranteeing that self is not used until the end of the fn suffices. (At least I hope that's true. =)

This is blocked on non-lexical lifetimes, which is in turn blocked on MIR. Ideally this will be fixed by the end of 2016, but a fix won't land soon.

Not very surprising, but I confirmed that the initial example does indeed compile on nightly if you turn the non-lexical lifetimes feature on.

I think I ran into a similar problem in production where an immutable borrow seems to live too long (workaround).

I should also mention that starwed's code no longer compiles on the latest nightly. @oberien suggested a "regression-from-nightly-to-nightly" tag, and it was also suggested I should tag @nikomatsakis in case this was an urgent problem :)

Stumbled upon this issue trying to do something very similar (which is not allowed by current nll):

fn f(vec: &mut Vec<u8>) -> &u8 {
    if let Some(n) = vec.iter_mut().find(|n| **n == 1) {
        *n = 10;
        n
    } else {
        vec.push(10);
        vec.last().unwrap()
    }
}

fn main() {
    let mut vec = vec![1, 2, 3];
    f(&mut vec);
}
error[E0499]: cannot borrow `*vec` as mutable more than once at a time
 --> src/main.rs:6:9
  |
1 | fn f(vec: &mut Vec<u8>) -> &u8 {
  |           - let's call the lifetime of this reference `'1`
2 |     if let Some(n) = vec.iter_mut().find(|n| **n == 1) {
  |                      --- first mutable borrow occurs here
3 |         *n = 10;
4 |         n
  |         - returning this value requires that `*vec` is borrowed for `'1`
5 |     } else {
6 |         vec.push(10);
  |         ^^^ second mutable borrow occurs here

error[E0502]: cannot borrow `*vec` as immutable because it is also borrowed as mutable
 --> src/main.rs:7:9
  |
1 | fn f(vec: &mut Vec<u8>) -> &u8 {
  |           - let's call the lifetime of this reference `'1`
2 |     if let Some(n) = vec.iter_mut().find(|n| **n == 1) {
  |                      --- mutable borrow occurs here
3 |         *n = 10;
4 |         n
  |         - returning this value requires that `*vec` is borrowed for `'1`
...
7 |         vec.last().unwrap()
  |         ^^^ immutable borrow occurs here

(we should have removed E-needstest when we removed NLL-fixed-by-NLL)

Was this page helpful?
0 / 5 - 0 ratings

Related issues

Leo1003 picture Leo1003  ·  898Comments

nikomatsakis picture nikomatsakis  ·  236Comments

nikomatsakis picture nikomatsakis  ·  340Comments

withoutboats picture withoutboats  ·  202Comments

nikomatsakis picture nikomatsakis  ·  274Comments