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

Inherent async fn returning `Self` treats type's lifetime parameters as `'static` #61949

Open
Arnavion opened this issue Jun 19, 2019 · 8 comments

Comments

Projects
None yet
5 participants
@Arnavion
Copy link

commented Jun 19, 2019

rustc 1.37.0-nightly (2887008e0 2019-06-12) and playground's 2019-06-17 b25ee644971a168287ee

Playground

#![feature(async_await)]

pub struct Foo<'a> {
    pub bar: &'a i32,
}

impl<'a> Foo<'a> {
    pub async fn new(bar: &'a i32) -> Self {
        Foo {
            bar
        }
    }
}
error[E0495]: cannot infer an appropriate lifetime due to conflicting requirements
 --> src/lib.rs:8:22
  |
8 |     pub async fn new(bar: &'a i32) -> Self {
  |                      ^^^
  |
note: first, the lifetime cannot outlive the lifetime 'a as defined on the impl at 7:6...
 --> src/lib.rs:7:6
  |
7 | impl<'a> Foo<'a> {
  |      ^^
  = note: ...so that the expression is assignable:
          expected &i32
             found &'a i32
  = note: but, the lifetime must be valid for the static lifetime...
  = note: ...so that the types are compatible:
          expected Foo<'_>
             found Foo<'static>

It works to either change the signature to take bar: &'static i32, or to change the body of the fn to use a static borrow like bar: &5. So the compiler really does want the function to return a Foo<'static>, even though Self is a Foo<'a>


The workaround is to not use Self:

-    pub async fn new(bar: &'a i32) -> Self {
+    pub async fn new(bar: &'a i32) -> Foo<'a> {

Mentoring notes: See notes here on Zulip.

@Arnavion

This comment has been minimized.

Copy link
Author

commented Jun 19, 2019

jebrosen pointed out on IRC that it also happens with RPIT in general, so this might be the same as #53613 (from async fn expanding to -> impl Future<Output = ...>)

@nikomatsakis

This comment has been minimized.

Copy link
Contributor

commented Jul 5, 2019

As I pointed out in #53613, there is a more dangerous variant of this involving projections:

#![feature(async_await)]

pub trait HasItem<'a> {
    type Item;
}

// Does not compile:
async fn example1<'a, T: HasItem<'a>>(item: T::Item) -> T::Item {
    item
}


fn main() { }

Note that this version compiles just fine

async fn example1<'a, T: HasItem<'a>>(item: T::Item) -> <T as HasItem<'a>>::Item {
    item
}
@nikomatsakis

This comment has been minimized.

Copy link
Contributor

commented Jul 5, 2019

There are some forward compatibility concerns here. In particular, callers can (somewhat incorrectly) assume that the returned futures are 'static. In other words, the callee is committing to returning a future that does not capture 'a in these examples -- right now, that typically means callee doesn't compile, but it could happen that the callee happens to compile, and then callers would be able to use this future in places where 'a is out of scope. If we later fix this bug, those callers will stop compiling.

@nikomatsakis

This comment has been minimized.

Copy link
Contributor

commented Jul 5, 2019

The bug involving Self is rather straightforward (but grungy) to fix. It's also possible for us to, temporarily, just create an error if you return a type mentioning Self in an async fn.

But fixing the T::Item case is rather more complex. We don't know until type-checking that T::Item resolves to <T as HasItem<'a>>::Item. And, if I had my druthers, we might not know until even later than that. I suppose we could do a forward compatibility restriction of some kind.

@nikomatsakis

This comment has been minimized.

Copy link
Contributor

commented Jul 9, 2019

An example of something which compiles now but I think should not (and would not, if this were fixed):

#![feature(async_await)]

pub struct Foo<'a> {
    pub bar: &'a i32,
}

impl<'a> Foo<'a> {
    pub async fn new(_bar: &'a i32) -> Self {
        Foo {
            bar: &22
        }
    }
}

async fn foo() {
  let x = {
    let bar = 22;
    Foo::new(&bar).await
  };
  drop(x);
}

fn main() { }

the problem here is that the type of x should be Foo<'bar> -- i.e., it should be constrained to the lifetime of bar, but it is actually Foo<'static>.

@nikomatsakis

This comment has been minimized.

Copy link
Contributor

commented Jul 9, 2019

I left some notes on how to fix this on Zulip

@nikomatsakis nikomatsakis added E-mentor and removed E-mentor labels Jul 9, 2019

@davidtwco

This comment has been minimized.

Copy link
Member

commented Jul 9, 2019

@rustbot claim

@nikomatsakis

This comment has been minimized.

Copy link
Contributor

commented Jul 9, 2019

OK so having thought this over I have come to the conclusion that the right behavior here is to give an error if people use Self or T::Foo projections from within an impl Trait such that they wind up "inheriting lifetimes". It's not an ideal solution but it'll permit many uses while forbidding the cases that are causing trouble and giving us time to fix properly.

The way I think we should do this:

During type checking, there is a check_opaque function that currently checks for cyclic opaque types.

In the case of existential types whose origin is either ReturnImplTrait or AsyncFn, we want to enforce this condition.

What we would do is to access the tcx.predicates_of the existential def-id. The predicates field of the result will contain the "bounds" of the impl trait (with the self-type being an opaque type). (Note that we don't want to inspect the parent, which will contain predicates defined in the enclosing scope.)

Actually, we may want to introduce a new query (existential_bounds_of or something) that just returns those predicates that tcx.predicates_of would return..? I guess it's fine to invoke tcx.predicates_of, it just feels a touch fragile. Ah well, that's what tests are for. =)

Anyway, we basically iterate over that predicates field using a type visitor. In there, we look for references to regions that are ReEarlyBound but where the index indicates it is coming from the parent. We would report an error if we found any such region.

One thing to watch out for though while we are visiting the type. If we have something like impl Foo, that will be represented by a predicate O: Foo where O is the opaque identity type. Such a type is constructed here:

let substs = InternalSubsts::identity_for_item(tcx, def_id);
let opaque_ty = tcx.mk_opaque(def_id, substs);

Such a type will contain the illegal regions and we need to ignore it. We basically want to ignore instances of the self-type when visiting -- i.e., we can construct the opaque identity type (call it opaque_identity_ty) and override visit_ty to skip visiting the contents if ty == opaque_identity_ty.

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