rust-lang / rust

Empowering everyone to build reliable and efficient software.
https://www.rust-lang.org
Other
97.76k stars 12.65k forks source link

RPITIT causes incorrect "dropped while still borrowed" error in some cases #131490

Open cdhowie opened 1 week ago

cdhowie commented 1 week ago

From this SO question, the following code does not compile:

trait Iterable {
    fn owned_iter(&self) -> impl Iterator<Item = usize> + 'static;
}

struct B<T> {
    x: T,
}

impl<T: Iterable + Clone> Iterable for B<T> {
    fn owned_iter(&self) -> impl Iterator<Item = usize> + 'static {
        let y = self.x.clone();
        y.owned_iter() // error[E0597]: `y` does not live long enough
    }
}

However, boxing the iterator as a trait object allows it to compile:

trait Iterable {
    fn owned_iter(&self) -> impl Iterator<Item = usize> + 'static;
}

struct B<T> {
    x: T,
}

impl<T: Iterable + Clone> Iterable for B<T> {
    fn owned_iter(&self) -> impl Iterator<Item = usize> + 'static {
        let y = self.x.clone();
        let b: Box<dyn Iterator<Item = usize>> = Box::new(y.owned_iter());
        b
    }
}

Notably, not cloning self.x and simply returning self.x.owned_iter() also works.

I don't see any reason why the first code block shouldn't compile, especially while the boxed version and the non-cloning version do.

Meta

rustc --version --verbose:

rustc 1.81.0 (eeb90cda1 2024-09-04)
binary: rustc
commit-hash: eeb90cda1969383f56a2637cbd3037bdf598841c
commit-date: 2024-09-04
host: x86_64-unknown-linux-gnu
release: 1.81.0
LLVM version: 18.1.7

It also fails to compile on 1.83.0-nightly (2024-10-09 eb4e2346748e1760f74f).

compiler-errors commented 6 days ago

This was fixed by https://github.com/rust-lang/rust/pull/116040, but that was not sound. Fixing this is particularly nontrivial.

This may be easier to address soon if https://github.com/rust-lang/rust/pull/131033 lands and is then stabilized, since you should be able to express the +'static as +use<Self> instead.

cdhowie commented 6 days ago

But the returned iterator in this case doesn't even borrow self, so it doesn't seem like that should be necessary. It also changes the semantic meaning of the returned type to borrow self when the intent of this code is exactly the opposite. Am I missing something?

compiler-errors commented 6 days ago

Am I missing something?

I think so :)

So the RPITIT wouldn't be capturing self: &Self, but instead it would be capturing Self, the implicit type parameter of the trait. That is to say, he capture we want to avoid is the elided lifetime 'a (which is elided in this case) like:

trait Iterable {
    fn owned_iter<'a>(&'a self) -> impl Iterator<Item = usize> + 'static;
}
lqd commented 6 days ago

Since https://github.com/rust-lang/rust/pull/131033 landed, this should work on nightly now

#![feature(precise_capturing_in_traits)]

trait Iterable {
    fn owned_iter(&self) -> impl Iterator<Item = usize> + use<Self>;
}

struct B<T> {
    x: T,
}

impl<T: Iterable + Clone> Iterable for B<T> {
    fn owned_iter(&self) -> impl Iterator<Item = usize> + use<T> {
        let y = self.x.clone();
        y.owned_iter()
    }
}