Rust: `&self` lifetime elision with two parameters does not work with `async fn`

Created on 8 Aug 2019  路  7Comments  路  Source: rust-lang/rust

Originally reported in https://github.com/rust-lang/rust/pull/63383#discussion_r312128268.

#![feature(async_await)]

#![feature(nll)]
// Without it you also get //~^ ERROR cannot infer an appropriate lifetime

struct A;

impl A {
    async fn foo(&self, f: &u32) -> &A {
        self
    }
}

should compile (it does if you remove async or write async fn foo<'a, 'b>(&'a self, f: &'b u32) -> &'a A) but does not (playground):

error[E0106]: missing lifetime specifier
 --> src/lib.rs:7:37
  |
7 |     async fn foo(&self, f: &u32) -> &A {
  |                                     ^
  |
  = note: return-position elided lifetimes require exactly one input-position elided lifetime, found multiple.

This seems like a rather serious usability bug as compared to what you expect from normal Rust.

cc https://github.com/rust-lang/rust/pull/63209
cc @nikomatsakis @cramertj

A-async-await A-inference A-lifetimes A-typesystem AsyncAwait-Focus C-bug F-async_await T-compiler requires-nightly

Most helpful comment

I have a nice fix now.

All 7 comments

The code that calculates LtReplacement in lowering is wrong-- it should give preference to the lifetime coming from self over other lifetimes from the function signature.

OK, so, a few thoughts:

I agree this is a serious usability bug. It's not a forward a compatibility hazard, though.

Regarding the code itself, it's a shame that we have to duplicate the elision code -- there are currently two bits of code doing the same check. The one in lowering is the one giving us the error here. The error is also phrased slightly differently than the one from lifetime resolution.

We could probably get this to work correctly for &self and &mut self. The logic for cases like self: &Self, as we've seen, is somewhat more complex, and before trying to handle that, I'd want to see if we can't reduce the duplication/

I feel like ultimately I'd hate to hold off on shipping async-await owing to this bug, but it'd be good to fix it.

@cramertj so I guess the buggy code is this:

https://github.com/rust-lang/rust/blob/60960a260f7b5c695fd0717311d72ce62dd4eb43/src/librustc/hir/lowering.rs#L2159-L2163

presumably the way to make it work for &self and &mut self would be to check the implicit_self field of the decl parameter. If it is ImmRef or MutRef, then I would guess we should just use LtReplacement::Some(self.lifetimes_to_define[lifetime_count_before_args]) always.

Er, my mistake, you can't match on the implicit_self field, but rather promote this computation earlier:

https://github.com/rust-lang/rust/blob/60960a260f7b5c695fd0717311d72ce62dd4eb43/src/librustc/hir/lowering.rs#L2190-L2214

(TBH I'm not 100% sure this code is right, so I'm doing a quick test locally to see if I'm remembering things correctly.)

Actually, the behavior of the current code is (I think) worse than the issue suggests. If I understand what it's doing -- it is looking for exactly one lifetime parameter that was not explicitly declared, and using that as the elided lifetime.

so for example this code compiles which should not (playground):

// build-pass (FIXME(62277): could be check-pass?)
// edition:2018

#![feature(async_await)]

struct Xyz {
    a: u64,
}

trait Foo {}

impl Xyz {
    async fn do_sth<'a>(
        &'a self, foo: &dyn Foo
    ) -> &dyn Foo
    {
        foo
    }
}

fn main() {}

as does this (playground):

// build-pass (FIXME(62277): could be check-pass?)
// edition:2018

#![feature(async_await)]

struct Xyz {
    a: u64,
}

trait Foo {}

impl Xyz {
    async fn do_sth<'a>(
        foo: &dyn Foo, bar: &'a dyn Foo
    ) -> &dyn Foo
    {
        foo
    }
}

fn main() {}

I have a nice fix now.

Was this page helpful?
0 / 5 - 0 ratings