-
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
NLL + impl Trait: Borrow checker error on else branch #53528
Comments
Hmm my guess is that this has something to do with the variance of |
discussed at meeting. assigning to self to investigate root cause. |
I've started looking at this While comparing the log output when compiling the two variants, I noticed this difference: Working variant with
non-working variant with
This led me to wonder: could we be forced to keep the region alive because the It was easy enough to make a variant example that tried to model this (play): #![feature(nll)]
#[derive(Default)]
struct X(Vec<u32>);
struct DropIter<'a, X: 'a>(std::slice::Iter<'a, X>);
// (just delegates to underlying iterator)
impl<'a, X> Iterator for DropIter<'a, X> {
type Item = &'a X;
fn next(&mut self) -> Option<&'a X> { self.0.next() }
}
impl<'a, X> Drop for DropIter<'a, X> {
fn drop(&mut self) { /* no-op, but code below cannot use that knowledge */ }
}
impl X {
// error will gone if uncomment
// fn get_iter(&self) -> Option<std::slice::Iter<'_, u32>> { Some(self.0.iter()) }
fn get_iter(&self) -> Option<DropIter<'_, u32>> { Some(DropIter(self.0.iter())) }
fn push(&mut self, x: u32) {
self.0.push(x);
}
}
fn main() {
let mut tmp = X::default();
if let Some(xx) = tmp.get_iter() { // `xx` holds the reference to `tmp`
for t in xx {
println!("{:?}", t);
}
} else {
tmp.push(1); // ERROR: cannot borrow `tmp` as mutable because it is also borrowed as immutable
};
} and indeed, when we wrap the iterator in a So now my new questions:
|
Ah, I think I know what's going on: Since That reasoning is applied for both the Therefore, on the This leads us to the following workaround for the problem as described: if you explicitly bind and then drop the #![feature(nll)]
#[derive(Default)]
struct X(Vec<u32>);
struct DropIter<'a, X: 'a>(std::slice::Iter<'a, X>);
// (just delegates to underlying iterator)
impl<'a, X> Iterator for DropIter<'a, X> {
type Item = &'a X;
fn next(&mut self) -> Option<&'a X> { self.0.next() }
}
impl<'a, X> Drop for DropIter<'a, X> {
fn drop(&mut self) { println!("dropped here"); }
}
impl X {
// error will gone if uncomment
// fn get_iter(&self) -> Option<std::slice::Iter<'_, u32>> { Some(self.0.iter()) }
fn get_iter(&self) -> Option<DropIter<'_, u32>> { Some(DropIter(self.0.iter())) }
fn push(&mut self, x: u32) {
self.0.push(x);
}
}
fn main() {
let mut tmp = X::default();
tmp.push(1);
println!("before if let");
match tmp.get_iter() { // `xx` holds the reference to `tmp`
Some(xx) => {
println!("before then for loop");
for t in xx {
println!("{:?}", t);
}
// but here `xx` is dropped
println!("end of then")
}
xx @ None => {
drop(xx); // this tells borrow-checker to release the borrow of tmp
tmp.push(1);
println!("end of else")
}
}
println!("after if let");
} |
and likewise here is that same workaround being applied to the original example (play): #![feature(nll)]
#[derive(Default)]
struct X(Vec<u32>);
impl X {
// error will gone if uncomment
// fn get_iter(&self) -> Option<std::slice::Iter<'_, u32>> {
fn get_iter(&self) -> Option<impl Iterator<Item = &u32>> {
Some(self.0.iter())
}
fn push(&mut self, x: u32) {
self.0.push(x);
}
}
fn main() {
let mut tmp = X::default();
match tmp.get_iter() { // `xx` holds the reference to `tmp`
Some(xx) => {
for t in xx {
println!("{:?}", t);
}
}
xx @ None => {
drop(xx); // expliciltly drop `None` so borrow-checker releases tmp
tmp.push(1);
}
};
} |
(In case its not clear, the compiler wants to keep the borrows of the Having said all that, I vaguely recall some discussion (perhaps elsewhere in this github repo) where people were suggesting that we could be smarter and have the compiler figure out that in cases like I haven't yet been able to find a reference for that discussion, but I'd like to link it here when/if I do...
But apart from that, I think that the above comments I wrote serve as an explanation for the bug. |
It might also help if the compiler actually said in its diagnostic that the compiler's reasoning includes the fact that it thinks that the drop is relevant. We actually do print out such information if we have a named local to refer to, like in the following (play): #![feature(nll)]
#[derive(Default)]
struct X(Vec<u32>);
impl X {
// error will gone if uncomment
// fn get_iter(&self) -> Option<std::slice::Iter<'_, u32>> {
fn get_iter(&self) -> Option<impl Iterator<Item = &u32>> {
Some(self.0.iter())
}
fn push(&mut self, x: u32) {
self.0.push(x);
}
}
fn main() {
let mut tmp = X::default();
match tmp.get_iter() { // `xx` holds the reference to `tmp`
Some(xx) => {
for t in xx {
println!("{:?}", t);
}
}
_yy @ None => {
tmp.push(1);
}
};
} where the compiler prints the following diagnostics:
So maybe we should consider doing the same for anonymous temporaries in cases like this...? (Is there a bug open for that?) Incidentally, I'm a little surprise that the span points to line 31 (the end of the whole |
Taking off of the RC2 milestone since I believe NLL is behaving in the manner we expect here (see #53528 (comment)), but also tagging as NLL-deferred in light of the suggestion I posed about future enhancements to our handling of enum variants that don't reference type parameters (see #53528 (comment) ) |
Actually, lets nominate this and talk about it with the NLL team; namely, this is the only follow-up question:
and if we are interested in investigating such an extension to NLL, we should open a dedicated issue to it rather than piggy-backing. (Note that this is not nominated for T-compiler. The |
(The aforementioned discussion of variant-sensitive-drop-analysis-extension was here: #53569 (comment)) |
but now that i've delved more deeply into #53569, I think it might be covering a dual problem...? Namely, the idea on #53569 is about dealing about a borrow into variant A when the sole But this bug (#53528) is currently musing about dealing a drop of variant B where B cannot possibly carry any state associated with the borrows taken from variant A. (In other words, these may not really be related at all...) |
Discussed at WG-nll meeting. We collectively decided that this is currently a "wont fix", but its a very interesting problem and a possible avenue for future extensions to Rust's static analysis. |
I'd like to add my summary of what is going on. Specifically, the problem is that the temporary value created by the call to |
Here is an example:
here is the error:
The text was updated successfully, but these errors were encountered: