Skip to content

impl Trait with multiple lifetimes imposes a strange lifetime requirement #61756

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

Open
goffrie opened this issue Jun 12, 2019 · 3 comments
Open
Labels
A-diagnostics Area: Messages for errors, warnings, and lints A-impl-trait Area: `impl Trait`. Universally / existentially quantified anonymous types with static dispatch. A-lifetimes Area: Lifetimes / regions A-NLL Area: Non-lexical lifetimes (NLL) C-enhancement Category: An issue proposing an enhancement or a PR with one. T-compiler Relevant to the compiler team, which will review and decide on the PR/issue.

Comments

@goffrie
Copy link
Contributor

goffrie commented Jun 12, 2019

Consider the following code:

pub struct Store;
impl Store {
    fn scan<'a>(&'a self) -> Box<dyn Iterator<Item = u64> + 'a> { panic!() }
}
pub struct Transaction<'a> {
    kv: &'a Store,
    reads: Vec<u64>,
}

impl<'a> Transaction<'a> {
    pub fn scan(&mut self) -> impl Iterator<Item = u64> + 'a + '_ {
        let iter = self.kv.scan();
        iter.map(move |k| {
            self.reads.push(k);
            k
        })
    }
}

Compiling with #![feature(nll)] yields the following error:

error: lifetime may not live long enough
  --> src/lib.rs:24:9
   |
18 |   impl<'a> Transaction<'a> {
   |        -- lifetime `'a` defined here
19 |       pub fn scan(&mut self) ->
   |                   - let's call the lifetime of this reference `'1`
...
24 | /         iter.map(move |k| {
25 | |             self.reads.push(k);
26 | |             k
27 | |         })
   | |__________^ returning this value requires that `'1` must outlive `'a`

error: aborting due to previous error

That is, it's requiring that the reference to Transaction outlive 'a. Unfortunately, it doesn't explain at all why that requirement was imposed.

Also, just returning + '_ complains:

error[E0700]: hidden type for `impl Trait` captures lifetime that does not appear in bounds
  --> src/lib.rs:20:9
   |
20 |         impl Iterator<Item = u64> + '_
   |         ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
   |
note: hidden type `std::iter::Map<std::boxed::Box<dyn std::iter::Iterator<Item = u64>>, [closure@src/lib.rs:24:18: 27:10 self:&mut Transaction<'a>]>` captures the lifetime 'a as defined on the impl at 18:6
  --> src/lib.rs:18:6
   |
18 | impl<'a> Transaction<'a> {
   |      ^^

This is odd to me because if we return Box<dyn Iterator<Item = u64> + '_>, everything works fine (and nobody complains about 'a). Furthermore, the equivalent to the impl Trait using an explicit existential type TransactionScan<'a, 'b>: Iterator<Item = u64>; works fine as well.

This may be related to/a dupe of #49431, but the errors produced are different so I'm not sure.

@estebank
Copy link
Contributor

Would using changing Transaction::scan to pub fn scan(&'a mut self) work for you?

@estebank estebank added A-diagnostics Area: Messages for errors, warnings, and lints A-iterators Area: Iterators A-lifetimes Area: Lifetimes / regions A-NLL Area: Non-lexical lifetimes (NLL) labels Jun 12, 2019
@goffrie
Copy link
Contributor Author

goffrie commented Jun 12, 2019

That makes the function compile, but it makes it quite unuseful for the caller: it means that any caller of scan() is committing to the Transaction being borrowed for as long as the lifetime of the Store reference within. For example, a function like

fn user(kv: &Store) {
    let mut tx = Transaction { kv, reads: vec![] };
    tx.scan().for_each(|x| println!("{:?}", x));
    tx.scan().for_each(|x| println!("{:?}", x));
}

fails to compile under that signature of fn scan:

error[E0499]: cannot borrow `tx` as mutable more than once at a time
  --> src/lib.rs:42:5
   |
41 |     tx.scan().for_each(|x| println!("{:?}", x));
   |     -- first mutable borrow occurs here
42 |     tx.scan().for_each(|x| println!("{:?}", x));
   |     ^^
   |     |
   |     second mutable borrow occurs here
   |     first borrow later used here

error: aborting due to previous error

(Also the error message here isn't great either - "first borrow later used here" isn't really pointing at the right thing because there isn't a later use of the mutable borrow, it's only required by the types.)

@estebank estebank added the T-compiler Relevant to the compiler team, which will review and decide on the PR/issue. label Oct 7, 2019
@kennytm
Copy link
Member

kennytm commented May 17, 2020

Related: #66551.

You could workaround using the Captures trait.

pub trait Captures<U: ?Sized> {}
impl<U: ?Sized, T: ?Sized> Captures<U> for T {}

impl<'a> Transaction<'a> {
    pub fn scan(&mut self) -> impl Iterator<Item = u64> + Captures<&'a Store> + '_ {
        ...
    }
}

(untagging A-iterators, the issue is not an issue of iterators)

@kennytm kennytm added A-impl-trait Area: `impl Trait`. Universally / existentially quantified anonymous types with static dispatch. and removed A-iterators Area: Iterators labels May 17, 2020
@crlf0710 crlf0710 added the C-enhancement Category: An issue proposing an enhancement or a PR with one. label Jun 11, 2020
copybara-service bot pushed a commit to google/crubit that referenced this issue Jul 15, 2022
See rust-lang/rust#61756 and the comments here.

PiperOrigin-RevId: 461149995
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
A-diagnostics Area: Messages for errors, warnings, and lints A-impl-trait Area: `impl Trait`. Universally / existentially quantified anonymous types with static dispatch. A-lifetimes Area: Lifetimes / regions A-NLL Area: Non-lexical lifetimes (NLL) C-enhancement Category: An issue proposing an enhancement or a PR with one. T-compiler Relevant to the compiler team, which will review and decide on the PR/issue.
Projects
None yet
Development

No branches or pull requests

4 participants