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

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

Open
kepexx opened this issue Jan 10, 2023 · 6 comments
Labels
A-impl-trait Area: `impl Trait`. Universally / existentially quantified anonymous types with static dispatch. A-lifetimes Area: Lifetimes / regions C-discussion Category: Discussion or questions that doesn't represent real issues. T-types Relevant to the types team, which will review and decide on the PR/issue.

Comments

@kepexx
Copy link

kepexx commented Jan 10, 2023

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)
@kepexx kepexx added the C-bug Category: This is a bug. label Jan 10, 2023
@Neo-Zhixing
Copy link
Contributor

Bump. Also ran into this issue.

@fmease fmease added A-lifetimes Area: Lifetimes / regions A-impl-trait Area: `impl Trait`. Universally / existentially quantified anonymous types with static dispatch. T-types Relevant to the types team, which will review and decide on the PR/issue. C-discussion Category: Discussion or questions that doesn't represent real issues. and removed needs-triage-legacy C-bug Category: This is a bug. labels Apr 17, 2024
@kmicklas
Copy link
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)

@fmease
Copy link
Member

fmease commented May 12, 2024

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 Thingimpl 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.

@fmease
Copy link
Member

fmease commented May 12, 2024

@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<'_> {

@kmicklas
Copy link
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.)

@Dylan-DPC
Copy link
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
Labels
A-impl-trait Area: `impl Trait`. Universally / existentially quantified anonymous types with static dispatch. A-lifetimes Area: Lifetimes / regions C-discussion Category: Discussion or questions that doesn't represent real issues. T-types Relevant to the types team, which will review and decide on the PR/issue.
Projects
None yet
Development

No branches or pull requests

6 participants