Stream: wg-async-foundations

Topic: `Self` and `'static` #61949


nikomatsakis (Jul 09 2019 at 17:13, on Zulip):

OK, I'll leave some notes here in a bit =)

boats (Jul 09 2019 at 17:25, on Zulip):

I'm not understanding the forward compatibility hazard here. Could you show an example of code that compiles, and a change that doesn't (or shouldn't) change the signature but stops compiling because the lifetime has changed?

nikomatsakis (Jul 09 2019 at 17:27, on Zulip):

it's a bit stretched, but if you have some thing like

impl Foo<'a> {
  async fn method(&self) -> Self { /* return some data that actually has type `Foo<'static>` */ }
}

then you can do

let f = {
let foo = Foo { .. };
foo.method()
};

without an error, even though it outlives the receiver foo

boats (Jul 09 2019 at 17:29, on Zulip):

Got it. So its not to do with the projections in particular

nikomatsakis (Jul 09 2019 at 17:49, on Zulip):

that example may not be quite right

nikomatsakis (Jul 09 2019 at 17:49, on Zulip):

you may need to await the returned future

nikomatsakis (Jul 09 2019 at 17:50, on Zulip):

actually it's worth trying to create a real example

nikomatsakis (Jul 09 2019 at 17:50, on Zulip):

with -> impl Trait (which is already stable) I think it's pretty clearly wrong

nikomatsakis (Jul 09 2019 at 17:50, on Zulip):

but the cat is out of the doggy door or whatever on that :)

nikomatsakis (Jul 09 2019 at 17:52, on Zulip):

OK yes this compiles but I think it should not:

#![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() { }
nikomatsakis (Jul 09 2019 at 17:59, on Zulip):

So I mean the major fix here would be to modify how lowering works

nikomatsakis (Jul 09 2019 at 18:09, on Zulip):

currently, we figure out which set of lifetimes are captured by a given impl Trait type using the lifetimes_from_impl_trait_bounds function

nikomatsakis (Jul 09 2019 at 18:09, on Zulip):

the "problem" is basically in visit_ty, which considers a type like Self to reference no lifetimes at all

nikomatsakis (Jul 09 2019 at 18:10, on Zulip):

I guess that Self is considered a Path

nikomatsakis (Jul 09 2019 at 18:14, on Zulip):

yeah, it looks like in astconv.rs we match against the name resolution result of Res::SelfTy(_, Some(impl_def_id))

nikomatsakis (Jul 09 2019 at 18:18, on Zulip):

during lowering, we start by lowering the impl header, and in particular we extract the self-type there

nikomatsakis (Jul 09 2019 at 18:19, on Zulip):

so I guess what we want to do is to have some kind of list of in-scope self types, similar to the list of in-scope regions

nikomatsakis (Jul 09 2019 at 18:19, on Zulip):

when we visit the impl items, at the same time as we push the impl header details, we would push the lowered self type (perhaps paired with the def-id of the impl) onto the list

nikomatsakis (Jul 09 2019 at 18:20, on Zulip):

then, in the lifetimes_from_impl_trait_bounds function above, if we encounter a HirTy::Path and it resolves to a Res::SelfTy(_, Some(def_id)), we would look for that def_id on this list. This would give us the self type. We could then just visit that self-type with the same visitor we have.

nikomatsakis (Jul 09 2019 at 18:20, on Zulip):

This is..not too horrible.

nikomatsakis (Jul 09 2019 at 18:20, on Zulip):

@Taylor Cramer does that look roughly like what you would expect :point_up: ?

nikomatsakis (Jul 09 2019 at 18:21, on Zulip):

I'm not entirely sure where the "async fn to something using impl trait" lowering takes place -- but I presume it's going to work out.

nikomatsakis (Jul 09 2019 at 18:26, on Zulip):

Er wait!

nikomatsakis (Jul 09 2019 at 18:26, on Zulip):

Maybe I'm just...wrong here.

nikomatsakis (Jul 09 2019 at 18:27, on Zulip):

Well, I think this fix I mention above is probably necessary for ordinary impl Trait, but there is a second problem, and that second problem is the reason that this afflicts async fn.

nikomatsakis (Jul 09 2019 at 18:28, on Zulip):

And this has to do with the "original hack" whereby a -> impl Trait inherits the lifetimes from its parent but substitutes 'static for them (because they are not meant to be observed). I am guessing we expand Self to Foo<'a> but then we substitute 'static somewhere.

nikomatsakis (Jul 09 2019 at 18:28, on Zulip):

Well, I think this fix I mention above is probably necessary for ordinary impl Trait, but there is a second problem, and that second problem is the reason that this afflicts async fn.

(In particular, doesn't async fn already capture all lifetimes in scope?)

davidtwco (Jul 09 2019 at 18:31, on Zulip):

@nikomatsakis do you still want me to take this or leave it for new folks since it’s tagged as E-mentor?

nikomatsakis (Jul 09 2019 at 18:34, on Zulip):

Let's remove that tag since now I'm not sure the best fix

davidtwco (Jul 09 2019 at 18:35, on Zulip):

Sure, should I assign myself?

nikomatsakis (Jul 09 2019 at 18:35, on Zulip):

Sure

nikomatsakis (Jul 09 2019 at 18:43, on Zulip):

I'm debating how to fix this, I'm not quite sure

nikomatsakis (Jul 09 2019 at 18:44, on Zulip):

I'm also not sure where this current setup is documented, though I feel like we wrote some big comments on it somewhere

nikomatsakis (Jul 09 2019 at 18:46, on Zulip):

Basically if you have

impl<'a> Foo<'a> {
  fn bar<'b>(&self) -> impl Trait<'a, 'b> { }
}

you wind up with an "existential type" that inherits the type+lifetime parameters from its surrounding scope, but then adds some more

nikomatsakis (Jul 09 2019 at 18:46, on Zulip):

so it's kind of like

nikomatsakis (Jul 09 2019 at 18:47, on Zulip):
impl<'a> Foo<'a> {
  fn bar<'b>(&self) -> impl Trait<'a, 'b> {
    existential Type<'a1, 'b1>;
  }
}

except not like that, because in Rust syntax, existential types (and other items in fns) don't inherit the scope from the fn they are within

nikomatsakis (Jul 09 2019 at 18:47, on Zulip):

but it means that the set of lifetime parameters on the existential type is actually 'a, 'b, 'a1, 'b1

nikomatsakis (Jul 09 2019 at 18:48, on Zulip):

and then any reference to that existential type uses 'static for the lifetimes taken from the parent, so e.g. you get

fn bar<'b>(&self) -> ExistentialType<'static, 'static, 'a, 'b>
nikomatsakis (Jul 09 2019 at 18:48, on Zulip):

this may seem totally bonkers, and in a way it is :)

nikomatsakis (Jul 09 2019 at 18:48, on Zulip):

but the reason is that we also inherit the types, and we want those.

nikomatsakis (Jul 09 2019 at 18:49, on Zulip):

anyway any place the user wrote 'a (say) will wind up being resolved to the 'a1 declared on the existential type

nikomatsakis (Jul 09 2019 at 18:49, on Zulip):

but, in this case, the user wrote Self

nikomatsakis (Jul 09 2019 at 18:49, on Zulip):

which we are internally expanding to a type that includes the 'a

nikomatsakis (Jul 09 2019 at 18:49, on Zulip):

We could certainly just make this an error for now -- as I intended to do w/ projections

nikomatsakis (Jul 09 2019 at 18:49, on Zulip):

Maybe that's the right thing to do

nikomatsakis (Jul 09 2019 at 18:50, on Zulip):

Not great but at least we don't do the wrong thing and we give room for a proper fix

nikomatsakis (Jul 09 2019 at 18:50, on Zulip):

We don't have to ban Self all together, we can just report errors when you have an async fn that references Self and Self contains lifetime parameters

nikomatsakis (Jul 09 2019 at 18:50, on Zulip):

same with T::Foo

nikomatsakis (Jul 09 2019 at 18:51, on Zulip):

OK, I'm convinced, I can write up some mentoring instructions for how to do that and we can leave a proper fix for future work :P

davidtwco (Jul 09 2019 at 19:22, on Zulip):

:+1:

nikomatsakis (Jul 09 2019 at 20:37, on Zulip):

@davidtwco left some notes here -- let me know if they make sense!

nikomatsakis (Jul 09 2019 at 20:37, on Zulip):

Not sure how familiar you are with how the generics etc are setup

davidtwco (Jul 09 2019 at 21:56, on Zulip):

Thanks, I’ll take a look.

nikomatsakis (Jul 10 2019 at 13:57, on Zulip):

Not sure how familiar you are with how the generics etc are setup

I think this was roughly the topic of my last rustc lecture series, as an aside, don't remember exactly what I covered here though

davidtwco (Jul 10 2019 at 14:16, on Zulip):

I think those instructions make sense.

Last update: Nov 18 2019 at 00:40UTC