How feature type_alias_impl_trait process lifetime parameter implicitly?

Hi everyone, as below code, I try to use generic_associated_types and type_alias_impl_trait features together to define a trait, it returns a Future which returns a lifetime bound Iterator. The version A is my original approach, then I found the weird thing: I have to define a useless lifetime parameter 'future in the definition of FutureIterator::Future, if I try to remove this parameter in Version B, I got a hidden type capturing error. I think: great, maybe type_alias_impl_trait helped me handled the 'future parameter implicitly in Version A. However, when I try to define the type of FutureIterator::Future explicitly with 'future, I got the big problem: I can not define a valid type of it as impl trait does (such as Version C). The final effect that impl trait does seems like HRTB over GAT: whatever 'future is, if it is valid in method get_iter then it works, but I can not manually use HRTB over GAT, or if my thoughts are wrong, what is the explicitly version of the imply trait does? Hope for anyone's help, thanks.

Version A:

#![feature(generic_associated_types)]
#![feature(type_alias_impl_trait)]

use std::future::Future;

trait FutureIterator: 'static {
    type Iterator<'iter>: Iterator<Item = ()>;
    type Future<'iter, 'future>: Future<Output = Self::Iterator<'iter>>;

    fn get_iter<'iter, 'future>(&'iter self, arg: &'future ()) -> Self::Future<'iter, 'future>;
}

struct It<'s> {
    _inner: &'s FutIt,
}

impl Iterator for It<'_> {
    type Item = ();

    fn next(&mut self) -> Option<Self::Item> {
        todo!()
    }
}

struct FutIt {}

impl FutureIterator for FutIt {
    type Iterator<'iter> = It<'iter>;

    type Future<'iter, 'future> = impl Future<Output = Self::Iterator<'iter>>;

    fn get_iter<'iter, 'future>(&'iter self, arg: &'future ()) -> Self::Future<'iter, 'future> {
        async move {
            let _ = &arg;
            It { _inner: self }
        }
    }
}

fn main() {}

(Playground)

Version B:

#![feature(generic_associated_types)]
#![feature(type_alias_impl_trait)]

use std::future::Future;

trait FutureIterator: 'static {
    type Iterator<'iter>: Iterator<Item = ()>;
    type Future<'iter>: Future<Output = Self::Iterator<'iter>>;

    fn get_iter<'iter>(&'iter self, arg: &()) -> Self::Future<'iter>;
}

struct It<'s> {
    _inner: &'s FutIt,
}

impl Iterator for It<'_> {
    type Item = ();

    fn next(&mut self) -> Option<Self::Item> {
        todo!()
    }
}

struct FutIt {}

impl FutureIterator for FutIt {
    type Iterator<'iter> = It<'iter>;

    type Future<'iter> = impl Future<Output = Self::Iterator<'iter>>;

    fn get_iter<'iter>(&'iter self, arg: &()) -> Self::Future<'iter> {
        async move {
            let _ = &arg;
            It { _inner: self }
        }
    }
}

fn main() {}

(Playground)

Errors:

   Compiling playground v0.0.1 (/playground)
error[E0700]: hidden type for `<FutIt as FutureIterator>::Future<'iter>` captures lifetime that does not appear in bounds
  --> src/main.rs:33:9
   |
32 |       fn get_iter<'iter>(&'iter self, arg: &()) -> Self::Future<'iter> {
   |                                            --- hidden type `impl Future<Output = It<'iter>>` captures the anonymous lifetime defined here
33 | /         async move {
34 | |             let _ = &arg;
35 | |             It { _inner: self }
36 | |         }
   | |_________^

For more information about this error, try `rustc --explain E0700`.
error: could not compile `playground` due to previous error

Version C:

#![feature(generic_associated_types)]
#![feature(type_alias_impl_trait)]

use std::future::Future;

trait FutureIterator: 'static {
    type Iterator<'iter>: Iterator<Item = ()>;
    type Future<'iter, 'future>: Future<Output = Self::Iterator<'iter>> + 'future;

    fn get_iter<'iter, 'future>(&'iter self, arg: &'future ()) -> Self::Future<'iter, 'future>;
}

struct It<'s> {
    _inner: &'s FutIt,
}

impl Iterator for It<'_> {
    type Item = ();

    fn next(&mut self) -> Option<Self::Item> {
        todo!()
    }
}

struct FutIt {}

impl FutureIterator for FutIt {
    type Iterator<'iter> = It<'iter>;

    type Future<'iter, 'future> = impl Future<Output = Self::Iterator<'iter>> + 'future;

    fn get_iter<'iter, 'future>(&'iter self, arg: &'future ()) -> Self::Future<'iter, 'future> {
        async move {
            print!("{:?}", &arg);
            It { _inner: self }
        }
    }
}

fn main() {}

(Playground)

Errors:

   Compiling playground v0.0.1 (/playground)
error[E0477]: the type `<FutIt as FutureIterator>::Future<'iter, 'future>` does not fulfill the required lifetime
  --> src/main.rs:30:35
   |
30 |     type Future<'iter, 'future> = impl Future<Output = Self::Iterator<'iter>> + 'future;
   |                                   ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
   |
note: type must outlive the lifetime `'future` as defined here as required by this binding
  --> src/main.rs:30:24
   |
30 |     type Future<'iter, 'future> = impl Future<Output = Self::Iterator<'iter>> + 'future;
   |                        ^^^^^^^

error: lifetime may not live long enough
  --> src/main.rs:33:9
   |
32 |       fn get_iter<'iter, 'future>(&'iter self, arg: &'future ()) -> Self::Future<'iter, 'future> {
   |                   -----  ------- lifetime `'future` defined here
   |                   |
   |                   lifetime `'iter` defined here
33 | /         async move {
34 | |             print!("{:?}", &arg);
35 | |             It { _inner: self }
36 | |         }
   | |_________^ associated function was supposed to return data with lifetime `'future` but it is returning data with lifetime `'iter`
   |
   = help: consider adding the following bound: `'iter: 'future`

For more information about this error, try `rustc --explain E0477`.
error: could not compile `playground` due to 2 previous errors

The difference between Version A and Version C is

-type Future<'iter, 'future>: Future<Output = Self::Iterator<'iter>>;
+type Future<'iter, 'future>: Future<Output = Self::Iterator<'iter>> + 'future;
 fn get_iter<'iter, 'future>(&'iter self, arg: &'future ()) -> Self::Future<'iter, 'future>;

And this change means that the associated Future<'iter, 'future> must be valid for 'future. However, this is only true when 'iter is at least as long as 'future. You can add that bound everywhere, but it technically changes the semantics of your code: you can no longer have a long-lived future over short-lived values. (On the other hand, how useful is that?)

There is a lack of expressiveness being hinted at here: If you have something like a Foo<'a, 'b>, the validity of Foo<'a, 'b> is the intersection of the validity for 'a and for 'b. But with + 'a + 'b type bounds, you can only express the union of lifetimes. You can introduce a third lifetime parameter to act as the intersection, but I don't know if I'd call that an improvement. An alternative trick is to use a dummy trait to let you mention the lifetime without imposing validity for the entirety of the lifetime.

In other situations, and with covariance, you would just unify the two lifetimes into one. The two input lifetimes would then coerce to some singular lifetime that's naturally in the intersection. However it seems reasonable here that you would want the lifetimes to remain different, so you could have a short-lived future returning longer-lived references.


In Version A, it does seem that mentioning the lifetime in the GAT signature is enough to consider the underlying impl Future to be constrained by the lifetime, even though it's not mentioned explicitly. That is a bit worisome to me [1], but the first thing I thought of trying isn't even implemented behind a feature yet it seems.

After a little more searching though, I think I'm basically talking about #96996 (and/or #49431).


  1. and GATs and impl Trait do still have a number of lifetime related bugs already ↩ī¸Ž

1 Like

Thank you, your reply helps me a lot. Yes, even though I am allowed to specify 'iter: 'future in the Version C, it becomes another totally different constraint: in the origin version, 'iter or 'future does not need to contain another one. I think the core of my question as your interpretation: Foo<'a, 'b> describes an intersection but Foo: 'a + 'b describes a union.