TAIT captures lifetime that it shouldn't capture

Hi, code like this gives an error (playground):

#![feature(type_alias_impl_trait)]

trait Foo {
    type Bar: 'static;
}

type Bar = impl Sized + 'static;

impl<'a> Foo for &'a () {
    type Bar = ();
}

#[define_opaque(Bar)]
fn foo<'a>(unit: &'a ()) -> impl Foo<Bar = Bar> + use<'a> {
    bar(unit)
}

fn bar<'a>(unit: &'a ()) -> impl Foo {
    unit
}

fn main() {}

Error:

 error[E0700]: hidden type for `Bar` captures lifetime that does not appear in bounds
   --> src/main.rs:15:5
    |
  7 | type Bar = impl Sized + 'static;
    |            -------------------- opaque type defined here
 ...
 14 | fn foo<'a>(unit: &'a ()) -> impl Foo<Bar = Bar> + use<'a> {
    |        -- hidden type `<impl Foo as Foo>::Bar` captures the lifetime `'a` as defined here
 15 |     bar(unit)
    |     ^^^^^^^^^

 For more information about this error, try `rustc --explain E0700`.
 error: could not compile `play` (bin "play") due to 1 previous error

While code like this compiles:

#![feature(type_alias_impl_trait)]

trait Foo {
    type Bar: 'static;
}

type Bar = impl Sized + 'static;

impl<'a> Foo for &'a () {
    type Bar = ();
}

#[define_opaque(Bar)]
fn foo<'a>(unit: &'a ()) -> impl Foo<Bar = Bar> + use<'a> {
    unit
}

fn main() {}

In both cases Bar should be resolved to the same value. Additionally borrow checker must have a hint that Bar can't capture anything, as it will not be well-formed without 'static. Is there some way I can somehow spell that bar will not have Bar capturing from 'a? So the code compiles.

Making Bar generic over a lifetime will not pass further checks for 'static in real code, it is in where bounds.

The compiler is correct here.

The bar function desugars to returning impl Foo + use<'a>. That is, it behaves as if the returned type is some generic Opaque<'a> type. And since the type is opaque, there's nothing to tell the compiler that its associated type is always the same and doesn't depend on the 'a lifetime.

Once I tell the compiler that the bar function returns a type whose Bar associated type doesn't depend on 'a, the code compiles. Rust Playground

2 Likes

In the first playground, when defining Bar in fn foo, it is effectively

// The type cannot be normalized outside of the defining scope (`fn bar`)
<UnnamedBarReturnType<'a> as Foo>::Bar

Note how even though it is known that this type meets a 'static bound, 'a is part of the type in a way that can't be normalized away within fn foo.

In the second playground, it is

<&'a () as Foo>::Bar
// which can be normalized (anywhere) to
()

While it cannot be part of the fully normalized type, the 'a can still be captured in some sense, as part of the "rigid alias" which cannot be fully normalized. And as it turns out, it is not sound to just throw out those kinds of captures in all cases. I wrote more about that here.

4 Likes