-
Notifications
You must be signed in to change notification settings - Fork 12.8k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
&mut self borrow conflicting with itself. #21906
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;
}
}
}
}
The second example only works because Rust can see that the loop will only run once, so |
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
}
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
} |
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. |
Some discussion on internals: https://internals.rust-lang.org/t/relaxing-the-borrow-checker-for-fn-mut-self-t/3256 |
Non lexical borrows feature discussion: rust-lang/rfcs#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 |
@nikomatsakis Could you please elaborate more on how NLL interacts with this issue? In my mental model of 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? |
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 fn two<'a>(&'a mut self) -> &'a i32 { .. } now observe that we call Does that help?
There are still a few steps to go before we can do this, but there is active work on doing the refactorings needed. |
@nikomatsakis Here's where I'm stuck: When we call |
@krdln we've actually borrowed from |
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 " |
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);
}
|
(we should have removed E-needstest when we removed NLL-fixed-by-NLL) |
I just ran into this and it really confused me for a while. I think a tip along the lines of "try changing the first borrow to an immutable borrow" would have helped me find a solution faster, at least in my case. Even though in retrospect that seems obvious... Examplefn last_big(vec: &mut Vec<i32>) -> &mut i32 {
match **vec {
[.., ref mut last] if *last > 99 => last,
_ => {
vec.push(100); // ERROR: can't borrow mutably again
vec.last_mut().unwrap()
}
}
} |
This bug has a lot of duplicates, and is problem case #3 in the NLL RFC. Should we close some of these duplicates? #51545 This was intentionally cut from NLL, which @shepmaster described in #51545 (comment). Should some of these duplicates be closed so that there's on canonical bug with all the relevant breadcrumbs (and perhaps an update on polonius 🤞)? cc @nikomatsakis. |
Sadly, latest nightly doesn't compile it. I wrote an Entry API for Vec instead |
I don't know why this happens but rovar and XMPPwocky on IRC believed it to be a compiler bug.
...gives the following error...
Interestingly, if the second method is changed to...
This will compile fine.
playpen: http://is.gd/mTkfw5
The text was updated successfully, but these errors were encountered: