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

Give a better error when HRTB require a function to be valid for 'static references #102201

Open
jyn514 opened this issue Sep 23, 2022 · 5 comments
Assignees
Labels
A-async-await Area: Async & Await A-closures Area: Closures (`|…| { … }`) A-diagnostics Area: Messages for errors, warnings, and lints A-higher-ranked Area: Higher-ranked things (e.g., lifetimes, types, trait bounds aka HRTBs) A-trait-system Area: Trait system AsyncAwait-Triaged Async-await issues that have been triaged during a working group meeting. D-newcomer-roadblock Diagnostics: Confusing error or lint; hard to understand for new users. D-terse Diagnostics: An error or lint that doesn't give enough information about the problem at hand. T-compiler Relevant to the compiler team, which will review and decide on the PR/issue.

Comments

@jyn514
Copy link
Member

jyn514 commented Sep 23, 2022

Given the following code: https://play.rust-lang.org/?version=stable&mode=debug&edition=2021&gist=cace533e183828ffeb2772f434b0611e

use std::future::Future;
use std::pin::Pin;

type BoxFuture<'a, T> = Pin<Box<dyn Future<Output = T> + Send + 'a>>;

#[derive(Default)]
struct B(bool);

#[derive(Default)]
struct A {
    a: bool,
    b: B,
}

impl A {
    fn b_mut(&mut self) -> &mut B {
        &mut self.b
    }
}

async fn entrypoint() {
    let mut a = A::default();
    let list = vec![];

    foo(&mut a, |b| Box::pin(baz(b, &list))).await;
}

async fn foo<'a, F>(a: &'a mut A, f: F)
where
     F: for<'b> FnOnce(&'b mut B) -> BoxFuture<'b, ()>,
{

    f(a.b_mut()).await;
}

async fn baz(b: &mut B, _: &[u8]) {
    b.0 = !b.0;
}

The current output is:

error[E0597]: `list` does not live long enough
  --> src/lib.rs:25:38
   |
25 |     foo(&mut a, |b| Box::pin(baz(b, &list))).await;
   |                 --- -----------------^^^^--
   |                 |   |                |
   |                 |   |                borrowed value does not live long enough
   |                 |   returning this value requires that `list` is borrowed for `'static`
   |                 value captured here
26 | }
   | - `list` dropped here while still borrowed

This is correct, but hard to understand.

Ideally the output should look like:

error[E0597]: `list` does not live long enough
  --> src/lib.rs:25:38
   |
25 |     foo(&mut a, |b| Box::pin(baz(b, &list))).await;
   |                 --- -----------------^^^^--
   |                 |   |                |
   |                 |   |                borrowed value does not live long enough
   |                 |   returning this value requires that `list` is borrowed for `'static`
   |                 value captured here
26 | }
   | - `list` dropped here while still borrowed
   |
   | = note: the lifetime cannot be shortened from `'static` to `'b` because closures are contravariant: https://doc.rust-lang.org/nomicon/subtyping.html#variance
   |
   | = note: the closure passed to `foo` must be valid for any lifetime, including the `'static` lifetime
28 | async fn foo<'a, F>(a: &'a mut A, f: F)
   |                                   ^^^^
   |      F: for<'b> FnOnce(&'b mut B) -> BoxFuture<'b, ()>,
   |         ^^^^^^^^^^^^^^^^^^^^^^^^^
   |         |
   |         due to the bound here
@jyn514 jyn514 added A-diagnostics Area: Messages for errors, warnings, and lints A-trait-system Area: Trait system A-closures Area: Closures (`|…| { … }`) T-compiler Relevant to the compiler team, which will review and decide on the PR/issue. A-async-await Area: Async & Await labels Sep 23, 2022
@jyn514
Copy link
Member Author

jyn514 commented Sep 23, 2022

In case it helps, here's one possible way to make the code compile: https://play.rust-lang.org/?version=stable&mode=debug&edition=2021&gist=66d32bc16434e74e2befb950629307b5
It won't work in general since the async block could borrow the slice.

@inquisitivecrystal inquisitivecrystal added D-newcomer-roadblock Diagnostics: Confusing error or lint; hard to understand for new users. D-terse Diagnostics: An error or lint that doesn't give enough information about the problem at hand. labels Sep 23, 2022
@vincenzopalazzo
Copy link
Member

@rustbot claim

as discussed with @eholk and @tmandry let's reproduce it and see how it goes with the result

@eholk
Copy link
Contributor

eholk commented Sep 26, 2022

@rustbot AsyncAwait-Triaged

@tmandry tmandry added the AsyncAwait-Triaged Async-await issues that have been triaged during a working group meeting. label Sep 26, 2022
@tmandry
Copy link
Member

tmandry commented Sep 26, 2022

The problem with the code is that the closure wants to return some data borrowed from its environment when it's being asked to return data that is valid for 'b, the lifetime of its argument.

The compiler tries to relate these two lifetimes, which aren't related at all ('b is not related to any other lifetime), and then falls back to thinking the return type must be 'static. This results in it saying that

  • list must be borrowed for 'static (Confusing)
  • list is dropped while still borrowed (Also confusing, because this message usually refers to the thing that borrowed list: the capturing closure in this case. But the idea that list must be borrowed past the end of the function has everything to do with the fact that the closure's return type is inferred to be 'static and nothing to do with the fact that it's captured. EDIT: if you write a version that captures &list but doesn't return it, the code compiles.)

I don't think contravariance is involved.

@danielhenrymantilla
Copy link
Contributor

Yeah, this compiles:

    let list = vec![];

    foo(&mut a, |b, []| Box::pin(baz(b, &list))).await;
}

async fn foo<'a, 'env, F>(a: &'a mut A, f: F)
where
     F: for<'b> FnOnce(&'b mut B, [&'b &'env (); 0]) -> BoxFuture<'b, ()>,
{

    f(a.b_mut(), []).await;
}

It's basically https://users.rust-lang.org/t/argument-requires-that-is-borrowed-for-static/66503/2?u=yandros

What Rust very sorely needs is for<'b where 'env : 'b> kind of bounds, and once we get them, the diagnostics could suggest it when running into this kind of scenario

@fmease fmease added the A-higher-ranked Area: Higher-ranked things (e.g., lifetimes, types, trait bounds aka HRTBs) label Sep 24, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
A-async-await Area: Async & Await A-closures Area: Closures (`|…| { … }`) A-diagnostics Area: Messages for errors, warnings, and lints A-higher-ranked Area: Higher-ranked things (e.g., lifetimes, types, trait bounds aka HRTBs) A-trait-system Area: Trait system AsyncAwait-Triaged Async-await issues that have been triaged during a working group meeting. D-newcomer-roadblock Diagnostics: Confusing error or lint; hard to understand for new users. D-terse Diagnostics: An error or lint that doesn't give enough information about the problem at hand. T-compiler Relevant to the compiler team, which will review and decide on the PR/issue.
Projects
None yet
Development

No branches or pull requests

7 participants