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

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

Open
dtolnay opened this issue Jul 27, 2019 · 4 comments

Comments

@dtolnay
Copy link
Member

commented Jul 27, 2019

#![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)

@nikomatsakis

This comment has been minimized.

Copy link
Contributor

commented Aug 13, 2019

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.

@nikomatsakis

This comment has been minimized.

Copy link
Contributor

commented Aug 14, 2019

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.

@nikomatsakis

This comment has been minimized.

Copy link
Contributor

commented Aug 14, 2019

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

@nikomatsakis

This comment has been minimized.

Copy link
Contributor

commented Aug 14, 2019

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.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
4 participants
You can’t perform that action at this time.