Skip to content

Async fn does not compile if lifetime does not appear in bounds (sometimes) #63033

Closed

Description

@dtolnay
#![feature(async_await)]

use std::marker::PhantomData;

trait Trait {}

async fn f<'a>(reference: &(), marker: PhantomData<dyn Trait>) {}

This fails with:

error[E0700]: hidden type for `impl Trait` captures lifetime that does not appear in bounds
 --> src/main.rs:7:64
  |
7 | async fn f<'a>(reference: &(), marker: PhantomData<dyn Trait>) {}
  |                                                                ^
  |
note: hidden type `impl std::future::Future` captures the scope of call-site for function at 7:64
 --> src/main.rs:7:64
  |
7 | async fn f<'a>(reference: &(), marker: PhantomData<dyn Trait>) {}
  |                                                                ^^

I believe this should compile because:

  1. The non-async function with the same signature does compile;

    fn f<'a>(reference: &(), marker: PhantomData<dyn Trait>) {}
  2. Seemingly insignificant tweaks, like adding a wrapper struct, make it compile.

    struct Wrapper(PhantomData<dyn Trait>);
    async fn f<'a>(reference: &(), marker: Wrapper) {}

Mentioning @cramertj who fixed a similar error message in #59001.
Mentioning @nikomatsakis who touched this error message in #49041 and #61775.

rustc 1.38.0-nightly (c43753f 2019-07-26)

Activity

  1. added
    C-bugCategory: This is a bug.
    T-compilerRelevant to the compiler team, which will review and decide on the PR/issue.
    on Jul 27, 2019
  2. nikomatsakis commented on Aug 13, 2019

    @nikomatsakis
    Contributor

    Check-in from WG-async-await meeting: I agree this is a bug -- it doesn't necessarily block stabilization (no fwd compat risk) but would definitely be a good one to fix. Marking as blocking + unclear for the moment and will return to it.

  3. nikomatsakis commented on Aug 14, 2019

    @nikomatsakis
    Contributor

    I did a bit of investigation. The error results because we have an inference variable ('_#3r) that seems to have no constraints, but must be equal to one of the captured regions (e.g., 'a). I haven't 100% figured out where this variable comes from, I imagine it's related to 'a though? In any case, the resolver basically winds up in a situation where it could pick any of the lifetimes but has no reason to prefer one over the other, and hence it reports an error.

  4. nikomatsakis commented on Aug 14, 2019

    @nikomatsakis
    Contributor

    OK, I think I see what's going on. It doesn't actually have much to do with the unused lifetime per se. What happens is this:

    • The type of the generated result is something like Foo<&'2 (), PhantomData<dyn Trait + '3>> -- i.e., it includes the types of the parameters, though with region variables in place of the known regions. Those types must be supertypes of the actual parameter types.
    • We capture the argument marker of type dyn Trait + 'static -- per the subtyping rules, we create the type dyn Trait + '3 for the type of this captured value, with the requirement that 'static: '3 -- as it happens, this is always true, so there is effectively no constraint on '3.
    • We do impose the constraint that '2 and '3 must both be equal to one of the free regions -- in this case, 'a or the anonymous lifetime of reference (which I'll call 'b).

    So thus the region solvers get in a state where '3 must be equal to 'a, 'b, or 'static -- but any one of them would be a reasonable choice.

    Removing the unused lifetime ('a) makes this an easy choice -- it can pick 'b, which is smaller than 'static. (Per the "least choice" rules we usually use.)

    Removing the type and putting it into a struct (Wrapper) removes the lifetime variable altogether, as then we have Foo<&'2 (), Wrapper>, and there are no lifetime parameters involved.

    I'm not sure what I think is the best fix. It would be nice for the solver to pick 'static, though it complicates the algorithm. You could certainly imagine a tweak that picks 'static if the value is legal and otherwise looks for a least choice. That might indeed be superior to the current approach, though I'd want to think about the full implications -- in particular, when impl Trait is used in places outside of the fn return type (e.g., in a let statement), picking 'static might lead to unnecessary borrow check errors. (The alternative would be to only look for 'static when there is no least choice; that seems safer.)

    (Interestingly, at some point at least I thought that a polonius-like approach sidesteps a lot of the challenges here-- if we had confidence that we would transition that might ease some of my concerns.)

  5. nikomatsakis commented on Aug 14, 2019

    @nikomatsakis
    Contributor

    Now that I understand better what is happening, I don't feel this has to block stabilization -- in particular, I don't think that there is a forward compatibility risk from us improving the region inference algorithm. I don't think we would choose an algorithm that makes existing code illegal. =)

    As such, I'm going to mark this as Deferred.

  6. added
    AsyncAwait-TriagedAsync-await issues that have been triaged during a working group meeting.
    and removed
    AsyncAwait-PolishAsync-await issues that are part of the "polish" area
    on Aug 14, 2019
  7. sfackler commented on Oct 10, 2019

    @sfackler
    Member

    Here's another example of the same problem that doesn't have an unused lifetime (AFAIKT):

    struct Foo;
    
    impl Foo {
        async fn wat(&self, _: &'static str, _: Bar<'_>) {}
    }
    
    struct Bar<'a>(Box<dyn std::fmt::Debug + 'a>);
    error[E0700]: hidden type for `impl Trait` captures lifetime that does not appear in bounds
     --> src/lib.rs:4:54
      |
    4 |     async fn wat(&self, _: &'static str, _: Bar<'_>) {}
      |                                                      ^
      |
    note: hidden type `impl std::future::Future` captures the scope of call-site for function at 4:54
     --> src/lib.rs:4:54
      |
    4 |     async fn wat(&self, _: &'static str, _: Bar<'_>) {}
      |                                                      ^^
    
    error: aborting due to previous error
    

    Removing any of the three arguments to the function allows it to compile.

  8. lovasoa commented on Dec 14, 2019

    @lovasoa
    Contributor

    I am also affected by this bug, where having both a reference and a box as parameters to a function causes the cryptic "hidden type for impl Trait captures lifetime that does not appear in bounds" :

    trait Trait {}
    async fn f<'a>(_a: &(), _b: Box<dyn Trait>) {}

    (playground)

    Is there a resolution in sight ?

  9. 58 remaining items

  10. added 2 commits that reference this issue on Feb 14, 2023
    86368ae
    83f10ea
  11. moved this from In progress (current sprint) to Done in wg-async workon Feb 15, 2023
  12. added a commit that references this issue on Dec 13, 2023
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Labels

A-async-awaitArea: Async & AwaitA-lifetimesArea: Lifetimes / regionsAsyncAwait-TriagedAsync-await issues that have been triaged during a working group meeting.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

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions