-
Notifications
You must be signed in to change notification settings - Fork 13k
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
The mutable borrow is released when matching on a Option<&mut Self> in a function, but not when the function is inlined #47680
Comments
(This needs investigation.) |
I'd like to work on this. |
@Aaron1011 sounds good - I was digging into this earlier actually but got distracted. I'll take a quick look at where I was, I think I was close to seeing what was going wrong =) |
Here's a reduced version of @shepmaster's example (playground) #![feature(nll)]
struct Foo;
impl Foo {
fn get_self(&mut self) -> Option<&mut Self> {
Some(self)
}
fn match_self(&mut self) -> &mut Self {
match self.get_self() {
Some(s) => s,
None => self.new_self()
}
}
fn new_self(&mut self) -> &mut Self {
self
}
fn trigger_bug(&mut self) {
let mut var = self;
// Comment out this loop...
loop {
var = var.match_self();
// ...or this statement to make this example compile
var = match var.get_self() {
Some(s) => s,
None => var.new_self()
}
}
}
}
fn main() {} |
I think this one is gonna be tricky. I think it's a similar problem to the one identified here nikomatsakis/borrowck#18. I feel like I can't quite describe it precisely, but what happens is something like this:
It may be that we want to make some kind of deeper change to the analysis here. |
I've managed to create an even simpler version. This example compiles: (playground): #![feature(nll)]
struct Foo;
impl Foo {
fn get_self(&mut self) -> Option<&mut Self> {
Some(self)
}
fn bad_method(&mut self) {
let mut var = self;
var = match var.get_self() {
Some(v) => v,
None => var
};
var = match var.get_self() {
Some(v) => v,
None => var
};
}
}
fn main() {} While this version does not: (playground): #![feature(nll)]
struct Foo;
impl Foo {
fn get_self(&mut self) -> Option<&mut Self> {
Some(self)
}
fn bad_method(&mut self) {
let mut var = self;
loop {
var = match var.get_self() {
Some(v) => v,
None => var
};
}
}
}
fn main() {} From looking at the generated MIR, the problem seems to be that the lifetime of the mutable borrow of |
@shepmaster later posted this example: #![feature(nll)]
#[derive(Debug)]
struct Thing;
impl Thing {
fn maybe_next(&mut self) -> Option<&mut Self> { None }
}
fn main() {
let mut thing = Thing;
let mut temp = &mut thing;
// if let works
while let Some(next) = temp.maybe_next() {
temp = next;
}
println!("{:?}", temp);
} Same basic idea. I think the challenge here is the same in all of them -- the code is not wrong per se, but it needs to be improved, and I'm not sure yet how to do it. In particular, the code infers (correctly) that the borrow created in iteration N of the loop may still be used in the This comes back to those posts I cited earlier. I think we want to improve the region inference to take into account...well, something. I'm just not quite sure what yet =) |
I have a rough idea I am kicking around: the regions for a borrow can potentially stop the DFS when they reach the borrow point. The idea being that either the borrow itself will cause an error or the path being borrowed must be reassigned between the previous borrow and that point. Have to try and refine it, but I think that's roughly the logic that lets us type the function call case. |
I think this is my preferred minimization, fyi: #![feature(nll)]
struct Thing;
impl Thing {
fn maybe_next(&mut self) -> Option<&mut Self> { None }
}
fn main() {
let mut temp = &mut Thing;
loop {
match temp.maybe_next() {
Some(v) => { temp = v; }
None => { }
}
}
} |
Another important test. This one must remain an error: #![feature(nll)]
fn create<'a>() -> &'a mut u32 { panic!() }
fn condition() -> bool { panic!() }
fn touch(_: &mut u32) { }
fn foo() {
// Should be error because:
// - if we pass through loop one time, then `q` points to `a`
let mut a = 22;
let mut x = &mut a;
let mut q = create();
loop {
let p = &mut *x;
if condition() { break; }
q = p;
x = create();
}
touch(&mut a);
touch(q);
}
fn main() {
} Some variations of proposed fixes I had did not pass this test. =) |
@nikomatsakis: That's essentially the conclusion that I came to - though I started with a different example. I had a solution for the first two minimizations working locally - it relied on encoding extra information in a I'm going to try out a few more ideas locally. Thanks for posting that minimization! |
@Aaron1011 I don't think we should be looking at the HIR. I have this "intuition" that the rule we want is to modify the dfs routine |
@nikomatsakis: I've created a branch that seems to work: https://github.com/Aaron1011/rust/tree/maybe_final_self_assign It correctly compiles all of the minimizations in this thread, and correctly generates an error for your last example. The core idea is that, under certain circumstances, we don't need to generate region constraints for assign statements. Specifically, if we have the statement However, I'm not entirely certain that the way I test if a variable is "derived from" another is sound. I do this in two parts:
The first part is used to eliminate false positives. It's possible for a particular reborrow to outlive the rhs (as determined by My main concern is that this might break if more constraints are somehow added. I haven't been able to find an example where this happens, but I haven't been able to rule it out, either. Do you think the basic idea of skipping constraint generation when a reborrow is involved is sound? |
@Aaron1011 I'll take a look -- might take a day or two though -- as you say, these are delicate changes. |
Sorry for not getting back to you @Aaron1011 -- it's been very busy. I'm moving this to NLL-deferred because it doesn't seem like a "must fix" blocker in terms of moving NLL towards stabilization (seeing as the code doesn't work with old borrow checker). That said, I have an experimental new formulation of NLL that does solve this problem, and I'd love to get your feedback on it. I hope to write it up soon. |
@nikomatsakis: Sounds good to me! Feel free to ping me on Github or IRC when it's ready. |
Another example that @nikomatsakis says is the same as this: pub struct Node {
next: Option<Box<Node>>,
}
fn example(mut list: &mut Node) {
if let Some(node) = &mut list.next {
list = node;
}
if let Some(node) = &mut list.next {
list = node;
}
}
Tested with 1.31.0-beta.14 and edition 2018 |
@shepmaster similar to #58787. This works: pub struct Node {
next: Option<Box<Node>>,
}
fn example(mut list: &mut Node) {
if let Some(ref mut node) = list.next {
list = node;
}
if let Some(ref mut node) = list.next {
list = node;
}
} This does not: pub struct Node {
next: Option<Box<Node>>,
}
fn example(mut list: &mut Node) {
if let Some(ref mut node) = list.next {
if true {
list = node;
}
}
if let Some(ref mut node) = list.next {
list = node;
}
} |
Hi folks, how is it going now? I recently found this code that failed to compile, either: fn get_default<'a, T: Default>(opt: &'a mut Option<T>) -> &'a mut T {
match opt.as_mut() {
Some(value) => value,
None => opt.insert(T::default()),
}
}
I don't think the second mutable borrow (line 4) on branch |
Originally from Stack Overflow
The text was updated successfully, but these errors were encountered: