rust-lang / rust-lang/rust

RPITIT "captures lifetime that does not appear in bounds"

Open
#128,752 8 comments 0 reactions 0 assignees View on GitHub

Nobody has claimed this yet.

A-diagnostics A-dyn-trait A-impl-trait A-trait-system C-bug T-compiler T-types
Dominant language
Rust
Stars
119k
Forks
16.1k
PR merge metrics
PR metrics pending

Description

I tried this code: playground

trait MyTrait {
    fn foo<'a, 'b>(&'a self, x: &'b i32) -> impl Future<Output = i32>;
}

trait ErasedMyTrait {
    fn foo<'life0, 'life1, 'dynosaur>(&'life0 self, x: &'life1 i32)
    -> Pin<Box<dyn Future<Output = i32> + 'dynosaur>>
    where
    'life0: 'dynosaur,
    'life1: 'dynosaur;
}

struct DynMyTrait<T: ErasedMyTrait> {
    ptr: T,
}

impl<T: ErasedMyTrait> MyTrait for DynMyTrait<T> {
    fn foo<'a, 'b>(&'a self, x: &'b i32) -> impl Future<Output = i32> {
        self.ptr.foo(x)
    }
}

Diagnostics

I got a nonsensical error message:

error[E0700]: hidden type for `impl Future<Output = i32>` captures lifetime that does not appear in bounds
  --> src/lib.rs:22:9
   |
21 |     fn foo<'a, 'b>(&'a self, x: &'b i32) -> impl Future<Output = i32> {
   |                --                           ------------------------- opaque type defined here
   |                |
   |                hidden type `Pin<Box<(dyn Future<Output = i32> + 'b)>>` captures the lifetime `'b` as defined here
22 |         self.ptr.foo(x)
   |         ^^^^^^^^^^^^^^^
   |
help: to declare that `impl Future<Output = i32>` captures `'b`, you can add an explicit `'b` lifetime bound
   |
21 |     fn foo<'a, 'b>(&'a self, x: &'b i32) -> impl Future<Output = i32> + 'b {
   |                                                                       ++++

This is an RPITIT so the lifetime should not have to appear in the bounds to be captured. As you might expect, adding the + 'b leads to a different error, and using the Captures trick does not work either (in fact, it is quite a mess of surprising errors: playground).

Should this compile?

Diagnostics aside, it's understandable that this wouldn't compile, because the compiler doesn't know what to do with the extra 'dynosaur lifetime in ErasedMyTrait::foo. It would have to generate an intersection lifetime to get the full return type of that method (the 'dynosaur in Pin<Box<dyn Future<Output = i32> + 'dynosaur>>.

Adding a lifetime that corresponds to 'dynosaur to the original trait does the trick:

trait MyTrait {
    fn foo<'a, 'b, 'd>(&'a self, x: &'b i32) -> impl Future<Output = i32>
    where 'a: 'd, 'b: 'd;
}

As I alluded to above, I don't think have we to know which lifetime 'dynosaur corresponds to to know that the original program is sound. Representing it as the intersection of 'a and 'b should be enough to see that our Pin<Box<dyn Future>> satisfies -> impl Future in the signature of the method we're implementing.

Through some magic it isn't necessary if we use async fn directly; this impl compiles fine:

impl<T: ErasedMyTrait> MyTrait for DynMyTrait<T> {
    async fn foo<'a, 'b>(&'a self, x: &'b i32) -> i32 {
        self.ptr.foo(x).await
    }
}

I'm pretty much expecting to hear that this is too hard to support now, but the async fn chicanery gives me hope that it can be supported sooner.

Meta

rustc version:

1.82.0-nightly
(2024-08-05 e57f3090aec33cdbf660)

Contributor guide

Open the contributing guide

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

Research direction

Reproduce the nightly example from the linked Rust Playground and compare the RPITIT implementation with the async fn implementation shown in the issue. Start by tracing how rustc handles the hidden future lifetime and the extra 'dynosaur lifetime; done means the supported case receives sound behavior and a useful diagnostic or compiles as specified.

Written by the indexing model from the issue text.

Assessment

Tech stack
rust
Domain
compilers
Issue type
Bug
Difficulty
5/5
Estimated time
Over a week
Activity status
Stale
Clarity
Mostly clear
Newbie friendliness
25/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.