Skip to content

Associated type with a 'static bound still captures lifetimes when using impl Trait. #106684

Open

Description

@kepexx

Using a returned impl Trait for a trait with an associated type and not specifying the associated type leads to the following error:

error[E0700]: hidden type for `impl Trait + 'static` captures lifetime that does not appear in bounds

..even when + 'static is specified, meaning no lifetimes should be captured. Adding #![feature(associated_type_bounds)] and instead using impl Trait<AssocType: 'static> as a return type solves the issue. I'm not sure if this is intended behavior - it seems pretty strange since AssocType is defined with a 'static bound. See the code below for a reproducible example.

#![feature(associated_type_bounds)]

trait Anything {}
impl<T: ?Sized> Anything for T {}

trait Thing {
    type Output: 'static;

    fn calc(&mut self) -> Self::Output;
}

impl<'a> Thing for &'a u8 {
    type Output = u8;
    
    fn calc(&mut self) -> u8 { **self }
}

// uncomment the bound to make it work
fn get_thing(x: &u8) -> impl Thing /* <Output: 'static> */ + '_ {
    x
}

fn f(n: &u8) -> impl Anything + 'static {
    let mut t = get_thing(n);
    t.calc()
}

Meta

rustc --version --verbose:

rustc 1.67.0-nightly (c090c6880 2022-12-01)

Activity

  1. PixelDust22 commented on Mar 30, 2023

    @PixelDust22
    Contributor

    Bump. Also ran into this issue.

  2. added
    A-lifetimesArea: Lifetimes / regions
    A-impl-traitArea: `impl Trait`. Universally / existentially quantified anonymous types with static dispatch.
    T-typesRelevant to the types team, which will review and decide on the PR/issue.
    C-discussionCategory: Discussion or questions that doesn't represent real issues.
    and removed
    C-bugCategory: This is a bug.
    on Apr 17, 2024
  3. kmicklas commented on May 12, 2024

    @kmicklas
    Contributor

    I am hitting this too. Additionally, adding an associated type bound like impl Trait<AssocType: 'static> doesn't always seem to work. Since the 2024 edition lifetime capture rules are likely to make this even worse, I also tried the proposed solution of type alias impl trait, but it has the same issue.

    trait Dummy {}
    impl Dummy for () {}
    
    trait AsStatic {
        type Static: 'static + Dummy;
    
        fn as_static(self) -> Self::Static;
    }
    
    struct Concrete<'a>(&'a ());
    
    impl<'a> AsStatic for Concrete<'a> {
        type Static = ();
    
        fn as_static(self) -> Self::Static {}
    }
    
    fn ret_concrete(r: &()) -> Concrete {
        Concrete(r)
    }
    
    trait Captures<U> {}
    impl<T: ?Sized, U> Captures<U> for T {}
    
    fn ret_impl(r: &()) -> impl Captures<&'_ ()> + AsStatic<Static: 'static> {
        Concrete(r)
    }
    
    fn use_concrete(r: &()) -> impl Dummy {
        ret_concrete(&r).as_static()
    }
    
    fn use_impl_rpit(r: &()) -> impl Dummy {
        ret_impl(&r).as_static() // ERROR
    }
    
    type Ret = impl Dummy;
    fn use_impl_tait(r: &()) -> Ret {
        ret_impl(&r).as_static() // ERROR
    }

    Error

    error[E0700]: hidden type for `impl Dummy` captures lifetime that does not appear in bounds
      --> view/src/bug.rs:36:5
       |
    35 | fn use_impl_rpit(r: &()) -> impl Dummy {
       |                     ---     ---------- opaque type defined here
       |                     |
       |                     hidden type `<impl Captures<&()> + AsStatic + 'static as AsStatic>::Static` captures the anonymous lifetime defined here
    36 |     ret_impl(&r).as_static() // ERROR
       |     ^^^^^^^^^^^^^^^^^^^^^^^^
       |
    help: to declare that `impl Dummy` captures `'_`, you can add an explicit `'_` lifetime bound
       |
    35 | fn use_impl_rpit(r: &()) -> impl Dummy + '_ {
       |                                        ++++
    
    error[E0700]: hidden type for `Ret` captures lifetime that does not appear in bounds
      --> view/src/bug.rs:41:5
       |
    39 | type Ret = impl Dummy;
       |            ---------- opaque type defined here
    40 | fn use_impl_tait(r: &()) -> Ret {
       |                     --- hidden type `<impl Captures<&()> + AsStatic + 'static as AsStatic>::Static` captures the anonymous lifetime defined here
    41 |     ret_impl(&r).as_static() // ERROR
       |     ^^^^^^^^^^^^^^^^^^^^^^^^
    

    Meta

    rustc --version

    rustc 1.80.0-nightly (78a775127 2024-05-11)
    
  4. fmease commented on May 12, 2024

    @fmease
    Member

    The code from the issue description compiles in the next edition — Rust 2024 — which is not stable yet. That's because the capturing rules for have been adjusted. See rust-lang/rfcs#3498 for details.

    I can't reproduce your supposed impl Thing ⟼ impl Thing<Output: 'static> fix.

    The lifetime bound 'static does not influence the set of captured generic arguments (which might be obvious by now).

    Further note that the code from the issue description can be “fixed” today by making get_thing return impl Thing + Captures<'_>, f return impl Anything + Captures<'_> (and I guess by removing various 'static bounds, taking a cursory glance they don't seem useful, I might be mistaken though) where trait Captures<'a> {} impl<'a, T: ?Sized> Captures<'a> for T {}. Note that impl Thing + '_ & impl Anything + '_ would also “work” but that solution wouldn't super general and breaks down if invariant (and contravariant?) generic arguments are involved.

    If you enable the feature precise_capturing, you can turn these two return types into impl use<'_> Thing and impl use<'_> Anything respectively. As alluded to in the first paragraph, Rust 2024 captures all input lifetimes automatically making the two occurrences of use<'_> redundant in this specific scenario.

  5. fmease commented on May 12, 2024

    @fmease
    Member

    @kmicklas I'm not sure what you mean by “the 2024 edition lifetime capture rules are likely to make this even worse”. In Rust 2024, use_impl_rpit from your example compiles contrary to Rust 2021 (:P).

    In Rust 2021, you can make use_impl_rpit compile using the Captures trick that you seem to be aware of (, “+ '_” if you don't care about the loss of generality) or by writing impl use<'_> Dummy under the feature precise_capturing.

    In the majority of cases, we do want to capture all in-scope generic params. The plan is to accept, finalize & stabilize the feature precise_capturing rather soon-ish IINM to give the user precise control over captures. This eliminates the need for the Captures trick and the need to turn an inline impl Trait into a TAIT.

    To make your TAIT snippet compile, you need to apply the following change:

    - type Ret = impl Dummy;
    + type Ret<'a> = impl Dummy;
      fn use_impl_tait(r: &()) -> Ret {

    Or under #![deny(elided_lifetimes_in_paths)]:

    - type Ret = impl Dummy;
    + type Ret<'a> = impl Dummy;
    - fn use_impl_tait(r: &()) -> Ret {
    + fn use_impl_tait(r: &()) -> Ret<'_> {
  6. kmicklas commented on May 17, 2024

    @kmicklas
    Contributor

    @fmease Maybe example wasn't clear, but explicitly capturing the lifetimes is not a solution because the goal is to have a 'static associated type which does not capture any lifetimes. Thanks for pointing me to the use<> syntax, that addresses the worries I had about the 2024 edition changes.

    I've since realized there is a solution that was not obvious to me before - nested use of impl:

    fn ret_impl(r: &()) -> impl Captures<&'_ ()> + AsStatic<Static = impl Dummy> {
        Concrete(r)
    }

    This achieves the goal of capturing lifetimes in the top level return type, but not its associated type.

    My naive intuition is still that the 'static bound on the associated type declaration itself should have been sufficient, but I think I understand better why it doesn't work like that. (It clearly wouldn't generalize to captured generic types, for example.)

  7. Dylan-DPC commented on Jan 9, 2025

    @Dylan-DPC
    Member

    Current output:

    error[E0700]: hidden type for `impl Anything + 'static` captures lifetime that does not appear in bounds
      --> src/lib.rs:25:5
       |
    23 | fn f(n: &u8) -> impl Anything + 'static {
       |         ---     ----------------------- opaque type defined here
       |         |
       |         hidden type `<impl Thing + '_ as Thing>::Output` captures the anonymous lifetime defined here
    24 |     let mut t = get_thing(n);
    25 |     t.calc()
       |     ^^^^^^^^
       |
    help: add a `use<...>` bound to explicitly capture `'_`
       |
    23 | fn f(n: &u8) -> impl Anything + 'static + use<'_> {
       |                                         +++++++++
    
    For more information about this error, try `rustc --explain E0700`.
    
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.A-lifetimesArea: Lifetimes / regionsC-discussionCategory: Discussion or questions that doesn't represent real issues.T-typesRelevant to the types 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