-
Notifications
You must be signed in to change notification settings - Fork 13.1k
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
Unexpected borrowck error when returning an impl Trait #53450
Comments
The root cause of the issue here is that during the compilation of We can observe the compiler's respecting that significance by applying a similar workaround to the one I applied to #53528: drop #![feature(nll)]
#![allow(unused_variables)]
fn main() {
let mut v = Vec::new();
let x = good(v.iter().cloned());
let y = bad(v.iter().cloned());
drop(y);
v.push(3);
}
fn good<I: Iterator<Item = usize>>(x: I) -> std::vec::IntoIter<usize> {
x.collect::<Vec<usize>>().into_iter()
}
fn bad<I: Iterator<Item = usize>>(x: I) -> impl Iterator<Item = usize> {
x.collect::<Vec<usize>>().into_iter()
} Or even just processing the iterator #![feature(nll)]
#![allow(unused_variables)]
fn main() {
let mut v = Vec::new();
let x = good(v.iter().cloned());
let y = bad(v.iter().cloned());
for _ in y { }
v.push(3);
}
fn good<I: Iterator<Item = usize>>(x: I) -> std::vec::IntoIter<usize> {
x.collect::<Vec<usize>>().into_iter()
}
fn bad<I: Iterator<Item = usize>>(x: I) -> impl Iterator<Item = usize> {
x.collect::<Vec<usize>>().into_iter()
} However, unlike #53528 where I could imagine a future language extension that "fixes" #53528, I don't think the compiler's reasoning is overly conservative in this case, at least for this code as written. Explanation: Part of the idea of #![feature(nll)]
#![allow(unused_variables, unused_mut)]
use std::fmt;
fn main() {
let mut v = vec![1, 2, 3, 4];
{
let x = good(v.iter().cloned());
let mut y = bad(v.iter().cloned());
// even trying to exhaust `y` will not suffice unless we actually
// *consume* it (and thus run its destructor):
for (i, elem) in y.by_ref().enumerate() {
println!("processing y[{}]: {:?}", i, elem);
}
println!("`y` is \"exhausted\" here, but not dead.");
println!("this is where you wanted to push");
// v.push(5);
println!("end of block");
}
}
fn good<I: Iterator<Item = usize>>(x: I) -> std::vec::IntoIter<usize> {
x.collect::<Vec<usize>>().into_iter()
}
fn bad<I: Iterator<Item = usize>>(x: I) -> impl Iterator<Item = usize> {
// x.collect::<Vec<usize>>().into_iter()
return DeadHands { iter: x, limit: 2 };
/// An iterator that yields at most `self.limit` items from given `iter`.
struct DeadHands<I: Iterator> where I::Item: fmt::Debug {
iter: I,
limit: usize
}
impl<I: Iterator> Drop for DeadHands<I> where I::Item: fmt::Debug {
fn drop(&mut self) {
for elem in self.iter.by_ref() {
println!("accessing elem {:?} from *original* iterator", elem)
}
}
}
impl<I: Iterator> Iterator for DeadHands<I> where I::Item: fmt::Debug {
type Item = I::Item;
fn next(&mut self) -> Option<I::Item> {
if self.limit > 0 {
self.limit -= 1;
self.iter.next()
} else {
None
}
}
}
} when one runs the above code in the playground, it prints:
thus illustrating that the destructor is holding onto the originally given iterator and doing stuff with it after the point in the control flow where you wished to mutate |
See also discussion on rust-lang/rfcs#1951 about which lifetimes are in scope for
|
My current plan is to close this issue as "not a bug" based on the reasoning given in #53450 (comment) ; basically, I think the problems here are issues with the expressiveness of (This bug does not currently have a milestone, priority, nor |
If you use the explicit version of #![feature(existential_type)]
fn main() {
let mut v = Vec::new();
let x = good(v.iter().cloned());
let y = bad(v.iter().cloned()); // if you comment out this line, it compiles.
v.push(3);
}
fn good<I: Iterator<Item = usize>>(x: I) -> std::vec::IntoIter<usize> {
x.collect::<Vec<usize>>().into_iter()
}
existential type Foo: Iterator<Item = usize>;
fn bad<I: Iterator<Item = usize>>(x: I) -> Foo {
x.collect::<Vec<usize>>().into_iter()
} |
Okay now that I've seen that our future plans include a way to specify the existential here with more explicit control over which lifetimes could be in the concrete type, I'm going to close this ticket as "not a bug." (See also #42940) |
The following playground highlights the issue: https://play.rust-lang.org/?gist=cf6be643e3a2fb6d69ac835cec890ad2&version=nightly&mode=debug&edition=2015
We have two implementations of the same function that are equivalent, except that one returns an
impl Iterator
while the other returns a concrete type. Calling the version that returnsimpl Trait
seems to incorrectly tie the lifetime of the return value to the function's argument.We have tested this on:
/cc: @nathansobo
The text was updated successfully, but these errors were encountered: