Join GitHub today
GitHub is home to over 31 million developers working together to host and review code, manage projects, and build software together.
Sign upThe mutable borrow is released when matching on a Option<&mut Self> in a function, but not when the function is inlined #47680
Comments
shepmaster
added
A-NLL
C-bug
WG-compiler-nll
labels
Jan 23, 2018
nikomatsakis
added this to the NLL: Valid code works milestone
Jan 23, 2018
This comment has been minimized.
This comment has been minimized.
|
(This needs investigation.) |
This comment has been minimized.
This comment has been minimized.
|
I'd like to work on this. |
This comment has been minimized.
This comment has been minimized.
|
@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 =) |
nikomatsakis
assigned
Aaron1011
Feb 1, 2018
This comment has been minimized.
This comment has been minimized.
|
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() {} |
This comment has been minimized.
This comment has been minimized.
|
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. |
This comment has been minimized.
This comment has been minimized.
|
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 |
This comment has been minimized.
This comment has been minimized.
|
@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 =) |
This comment has been minimized.
This comment has been minimized.
|
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. |
This comment has been minimized.
This comment has been minimized.
|
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 => { }
}
}
} |
This comment has been minimized.
This comment has been minimized.
|
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. =) |
This comment has been minimized.
This comment has been minimized.
|
@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! |
This comment has been minimized.
This comment has been minimized.
|
@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 |
This comment has been minimized.
This comment has been minimized.
|
@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? |
This comment has been minimized.
This comment has been minimized.
|
@Aaron1011 I'll take a look -- might take a day or two though -- as you say, these are delicate changes. |
nikomatsakis
added
the
NLL-complete
label
Mar 14, 2018
nikomatsakis
removed this from the NLL: Valid code works milestone
Apr 3, 2018
nikomatsakis
added
NLL-deferred
and removed
NLL-complete
labels
Apr 3, 2018
This comment has been minimized.
This comment has been minimized.
|
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. |
This comment has been minimized.
This comment has been minimized.
|
@nikomatsakis: Sounds good to me! Feel free to ping me on Github or IRC when it's ready. |
KiChjang
modified the milestone:
Edition RC 2
Aug 6, 2018
nikomatsakis
removed
the
WG-compiler-nll
label
Aug 27, 2018
This comment has been minimized.
This comment has been minimized.
|
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 |
matthewjasper
added
NLL-polonius
and removed
NLL-deferred
labels
Dec 1, 2018
jonhoo
referenced this issue
Jan 28, 2019
Closed
Borrow checker doesn't realize borrow ends in both branches #57956
This comment has been minimized.
This comment has been minimized.
justhsu
commented
Mar 29, 2019
•
|
@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;
}
} |
shepmaster commentedJan 23, 2018
Originally from Stack Overflow