rust-lang / rust

Empowering everyone to build reliable and efficient software.
https://www.rust-lang.org
Other
98.69k stars 12.75k forks source link

Improve diagnostics for impl Trait capturing lifetimes #78402

Open djc opened 4 years ago

djc commented 4 years ago

I tried this code:

use futures_util::stream::{once, Stream};
use std::io;

fn assert_static<T: 'static>(value: T) {}

async fn foo(a: &str) {
    assert_static(bar(a.split(',')));
}

fn bar<T>(_: T) -> impl Stream<Item = Result<Vec<u8>, io::Error>> {
    once(async { Ok(vec![]) })
}

I expected to see this happen: compiles correctly.

Instead, this happened:

error[E0759]: `a` has an anonymous lifetime `'_` but it needs to satisfy a `'static` lifetime requirement
 --> src/lib.rs:6:14
  |
6 | async fn foo(a: &str) {
  |              ^  ---- this data with an anonymous lifetime `'_`...
  |              |
  |              ...is captured here...
7 |     assert_static(bar(a.split(',')));
  |     ------------- ...and is required to live as long as `'static` here

Meta

rustc 1.47.0. This appears to be https://github.com/rust-lang/rust/issues/42940, fixing which might depend on type-alias-impl-trait (although so far that's only a workaround, not really a fix?). It would be nice to have more specific diagnostics here.

jyn514 commented 4 years ago

The error is correct, I think. You happen to call foo with a static value, but you don't put that in the type signature, so someone else is free to call it with a shorter lifetime. If you want to enforce only static lifetimes for foo, you can use a: &'static.

jyn514 commented 4 years ago

Oh wait, I missed that the return type of bar doesn't (or at least, shouldn't) depends on a. Yeah this does seem like a bug then.

Arnavion commented 4 years ago

This appears to be #42940, fixing which might depend on type-alias-impl-trait (although so far that's only a workaround, not really a fix?).

As cramertj wrote there, the current behavior is by design of the RPIT RFC ("Assumption 2"), because it was expected that type-alias-impl-trait would resolve it ("Assumption 1") correctly.

tmandry commented 4 years ago

Based on the RFC I think it's correct that this wouldn't compile. However, I am curious why adding a 'static bound to the return type of bar doesn't fix it:

fn bar<T>(_: T) -> impl Stream<Item = Result<Vec<u8>, io::Error>> + 'static {

That ought to remove any doubt that the return type is tied to the lifetime of T.

Also, this still fails to compile when foo is not async, but prints a more concise error message:

error[E0621]: explicit lifetime required in the type of `a`
 --> src/lib.rs:7:5
  |
6 | fn foo(a: &str) {
  |           ---- help: add explicit lifetime `'static` to the type of `a`: `&'static str`
7 |     assert_static(bar(a.split(',')));
  |     ^^^^^^^^^^^^^ lifetime `'static` required
masklinn commented 2 years ago

@tmandry from https://github.com/rust-lang/rust/issues/42940#issuecomment-335390296

From some experimentation, it seems to be because fn foo<T>(_: T) -> impl Bar compiles to something like fn foo<T>(_: T) -> impl Bar + 't, where 't is the lifetime of T. (I don't think this relationship can be expressed in regular Rust code, though Ralith on IRC suggested one could consider the anonymous type to contain PhantomData<T>)

Thus in the original code fn post<'a, B>(&'a self, _body: &B) -> impl Future + 'a effectively forces the return type to be bounded by B's lifetime in addition to 'a. This is why changing _body: &B to _body: B does not change anything, nor does annotating the parameter as _body: &'b B for an unconstrained 'b

(emphasis mine)

So when adding an explicit lifetime bound, it's in addition to the implicit one, not instead of. And since 'static is the widest lifetime it has no effect. I tried the same thing when I hit the issue, with the same results, before I found #42940 :/

Dylan-DPC commented 1 year ago

Current output:

error[[E0521]](https://doc.rust-lang.org/stable/error_codes/E0521.html): borrowed data escapes outside of function
 --> src/lib.rs:7:5
  |
6 | async fn foo(a: &str) {
  |              -  - let's call the lifetime of this reference `'1`
  |              |
  |              `a` is a reference that is only valid in the function body
7 |     assert_static(bar(a.split(',')));
  |     ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  |     |
  |     `a` escapes the function body here
  |     argument requires that `'1` must outlive `'static`

For more information about this error, try `rustc --explain E0521`.
error: could not compile `playground` due to previous error