Skip to content
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

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

Open
goffrie opened this issue Jun 12, 2019 · 3 comments
Open

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

goffrie opened this issue Jun 12, 2019 · 3 comments
Labels
A-diagnostics A-impl-trait A-lifetimes A-NLL C-enhancement T-compiler

Comments

@goffrie
Copy link
Contributor

@goffrie 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

@estebank estebank commented Jun 12, 2019

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

@estebank estebank added A-diagnostics A-iterators A-lifetimes A-NLL labels Jun 12, 2019
@goffrie
Copy link
Contributor Author

@goffrie 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 label Oct 7, 2019
@kennytm
Copy link
Member

@kennytm 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 and removed A-iterators labels May 17, 2020
@crlf0710 crlf0710 added the C-enhancement label Jun 11, 2020
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
A-diagnostics A-impl-trait A-lifetimes A-NLL C-enhancement T-compiler
Projects
None yet
Development

No branches or pull requests

4 participants