Skip to content

impl Trait + 'static is not 'static if returned from a generic function #76882

Closed

Description

@WaffleLapkin

I tried this code:

trait Trait {}
impl Trait for () {}

fn get_trait<I>() -> impl Trait + 'static {
    ()
}

fn assert_static<A: 'static>(_: A) {}

fn test<T>(_: T) {
    assert_static(get_trait::<T>());
}

(playground)

I expected that rustc will compile this code. But it doesn't. Instead, I've got this error:

error[E0310]: the parameter type `T` may not live long enough
  --> src/lib.rs:11:5
   |
10 | fn test<T>(_: T) {
   |         - help: consider adding an explicit lifetime bound...: `T: 'static`
11 |     assert_static(get_trait::<T>());
   |     ^^^^^^^^^^^^^ ...so that the type `impl Trait` will meet its required lifetime bounds

The error seems weird because the return type of get_trait is explicitly marked as 'static and T/I is not used anywhere at all.

This may be related to #49431 though I'm not entirely sure.

Meta

Checked on 1.46.0 stable and 1.48.0-nightly (2020-09-17 f3c923a)

Activity

  1. added
    A-impl-traitArea: `impl Trait`. Universally / existentially quantified anonymous types with static dispatch.
    on Sep 18, 2020
  2. added
    T-compilerRelevant to the compiler team, which will review and decide on the PR/issue.
    on Sep 18, 2020
  3. est31 commented on Sep 19, 2020

    @est31
    Member

    You need to add a :'static requirement to the generic T. This works:

    trait Trait {}
    impl Trait for () {}
    
    fn get_trait<I: 'static>() -> impl Trait + 'static {
        ()
    }
    
    fn assert_static<A: 'static>(_: A) {}
    
    fn test<T: 'static>(_: T) {
        assert_static(get_trait::<T>());
    }

    The core reason for this behaviour is the variance behaviour of impl Trait in return position: the returned impl Trait is always variant over all generic input parameters, even if not technically used. This is done so that if you change the internal implementation of a public API returning impl Trait you don't have to worry about introducing an additional variance param to the API. This could break downstream code and is thus not desireable for Rust's semver system.

  4. WaffleLapkin commented on Sep 19, 2020

    @WaffleLapkin
    MemberAuthor

    That makes some sense, but in my opinion, this should be only 'behaviour by default'. If the lifetime is explicitly set (+ 'a/+ 'static) then the programmer that has set it is responsible to set it right and do not break API in the future.

    Also, note that with -> impl Trait + 'static you can't return non static generic params, so there is no way to use them without changing signature (and so the limitation discussed doesn't really help with semver):

    fn erase<I: Trait>(x: I) -> impl Trait + 'static {
        x
    }
    error[E0310]: the parameter type `I` may not live long enough
     --> src/lib.rs:4:29
      |
    4 | fn erase<I: Trait>(x: I) -> impl Trait + 'static {
      |          --                 ^^^^^^^^^^^^^^^^^^^^ ...so that the type `I` will meet its required lifetime bounds
      |          |
      |          help: consider adding an explicit lifetime bound...: `I: 'static +`
    

    (playground)

  5. WaffleLapkin commented on Dec 11, 2020

    @WaffleLapkin
    MemberAuthor

    Note that this behaviour is also inconsistent with lifetimes which are not captured by default:

    trait Trait {}
    impl Trait for &() {}
    
    fn get_trait<I>(a: &()) -> impl Trait {
        a
    }
    error[E0759]: `a` has an anonymous lifetime `'_` but it needs to satisfy a `'static` lifetime requirement
     --> src/lib.rs:5:5
      |
    4 | fn get_trait<I>(a: &()) -> impl Trait {
      |                    --- this data with an anonymous lifetime `'_`...
    5 |     a
      |     ^ ...is captured here...
      |
    note: ...and is required to live as long as `'static` here
     --> src/lib.rs:4:28
      |
    4 | fn get_trait<I>(a: &()) -> impl Trait {
      |                            ^^^^^^^^^^
    help: to declare that the `impl Trait` captures data from argument `a`, you can add an explicit `'_` lifetime bound
      |
    4 | fn get_trait<I>(a: &()) -> impl Trait + '_ {
      |                                       ^^^^
    

    (playground)

  6. ais523 commented on Mar 27, 2023

    @ais523

    It seems that the current behaviour of capturing lifetimes from generic arguments as lifetimes in the impl Trait has been only partially implemented, and produces some strange consequences in practice. (I noticed the issue while answering this StackOverflow question, which was wondering why the return value of this sort of function couldn't be returned as an impl Trait + 'static but could be coerced to a dyn Trait + 'static inside a Box.)

    Here's an example of some very strange behaviour caused by this sort of capture:

    #![feature(type_alias_impl_trait)]
    #![feature(unsize)]
    
    use core::marker::Unsize;
    use core::fmt::Debug;
    
    pub fn returns_unrelated_impl<T>() -> impl Debug + 'static { () }
    
    // doesn't compile: the compiler thinks the `impl Debug` should capture `'a`
    pub fn test_1<'a>() -> impl Debug {
        returns_unrelated_impl::<&'a ()>()
    }
    
    // doesn't compile: the compiler thinks `ImplUnsizeStatic` should capture `'a`
    pub fn test_2a<'a>() -> impl Debug {
        type ImplUnsizeStatic = impl Unsize<dyn Debug> + 'static;
    
        let rv1: ImplUnsizeStatic = returns_unrelated_impl::<&'a ()>();
        let rv2: Box<dyn Debug> = Box::new(rv1);
        rv2
    }
    
    // does compile, because we aren't assigning an `impl` to another `impl`
    pub fn test_2b<'a>() -> impl Debug {
        fn verify_impl_unsize_static<T: Unsize<dyn Debug> + 'static>(x: &T) {}
    
        let rv1 = returns_unrelated_impl::<&'a ()>();
        verify_impl_unsize_static(&rv1);
        let rv2: Box<dyn Debug> = Box::new(rv1);
        rv2
    }

    (playground)

    The check that the return value of returns_unrelated_impl captures the generic argument T seems only to be made when the impl Debug it returns is coerced to another impl Trait type. test_2a shows that the return value of returns_unrelated_impl is not usable as an impl Unsize<dyn Debug> + 'static. Meanwhile, test_2b shows that the return value does in fact implement Unsize<dyn Debug> + 'static (a fact that makes it possible to box the return value as a Box<dyn Debug> + 'static and then return it as an impl Debug). It is very astonishing behaviour for these two cases to be considered to be different by the compiler.

    This makes me concerned that if "an impl Trait return type captures the generic type arguments of the function it's returned from" is intended behaviour, that restriction has not actually been implemented correctly, and thus return-position impl Trait has been accidentally stabilised in a state that permits more code than it's supposed to. If the capturing of generic arguments were required for soundness, this would likely be a soundness hole: however, it doesn't obviously appear to be required for soundness, so there may be simpler fixes (e.g. to not capture the arguments at all, which is what most programmers including me seem to expect will happen).

  7. Ddystopia commented on Sep 11, 2023

    @Ddystopia
    Contributor

    Another example, when that behavior is inappropriate

    #![feature(return_position_impl_trait_in_trait)]
    
    trait Foo: 'static { }
    
    trait Trait {
        fn foo(&mut self) -> impl Foo + 'static;
    
        fn bar(&mut self, foo: impl Foo + 'static) {
            std::hint::black_box((self, foo));
        }
    }
    
    struct FooImpl;
    impl Foo for FooImpl { }
    
    struct TraitImpl;
    impl Trait for TraitImpl {
        fn foo(&mut self) -> impl Foo + 'static {
            FooImpl
        }
    }
    
    fn main() {
        let mut t = TraitImpl;
        let foo = t.foo();
        t.bar(foo);
    }

    (playground)

    (here Foo: 'static so other bound are not needed, but I've included both for completeness)

    Here default behavior stands in the way of me creating an API, that would be possible without rpitit - you could change return type of Trait::foo to FooImpl or use associated type Trait::TraitsFoo and it will compile. But now I can't do it, because it captures mutable lifetime, even if it is clear that impl Foo is 'static, so it is not either & or &mut.

  8. changed the title [-]impl Trait + 'static is not static if returned from generic function[/-] [+]`impl Trait + 'static` is not `'static` if returned from a generic function[/+] on Sep 15, 2023
  9. compiler-errors commented on Dec 31, 2024

    @compiler-errors
    Contributor

    Fixed by #95474.

  10. WaffleLapkin commented on Dec 31, 2024

    @WaffleLapkin
    MemberAuthor

    nice

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    A-impl-traitArea: `impl Trait`. Universally / existentially quantified anonymous types with static dispatch.C-bugCategory: This is a bug.T-compilerRelevant to the compiler team, which will review and decide on the PR/issue.

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions