Skip to content

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

Open

Description

@jyn514

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

Activity

  1. added
    A-diagnosticsArea: Messages for errors, warnings, and lints
    A-closuresArea: Closures (`|…| { … }`)
    T-compilerRelevant to the compiler team, which will review and decide on the PR/issue.
    on Sep 23, 2022
  2. jyn514 commented on Sep 23, 2022

    @jyn514
    MemberAuthor

    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.

  3. added
    D-newcomer-roadblockDiagnostics: Confusing error or lint; hard to understand for new users.
    D-terseDiagnostics: An error or lint that doesn't give enough information about the problem at hand.
    on Sep 23, 2022
  4. vincenzopalazzo commented on Sep 26, 2022

    @vincenzopalazzo
    Member

    @rustbot claim

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

  5. eholk commented on Sep 26, 2022

    @eholk
    Contributor

    @rustbot AsyncAwait-Triaged

  6. added
    AsyncAwait-TriagedAsync-await issues that have been triaged during a working group meeting.
    on Sep 26, 2022
  7. tmandry commented on Sep 26, 2022

    @tmandry
    Member

    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.

  8. danielhenrymantilla commented on Oct 1, 2022

    @danielhenrymantilla
    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

  9. added
    A-higher-rankedArea: Higher-ranked things (e.g., lifetimes, types, trait bounds aka HRTBs)
    on Sep 24, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    A-async-awaitArea: Async & AwaitA-closuresArea: Closures (`|…| { … }`)A-diagnosticsArea: Messages for errors, warnings, and lintsA-higher-rankedArea: Higher-ranked things (e.g., lifetimes, types, trait bounds aka HRTBs)A-trait-systemArea: Trait systemAsyncAwait-TriagedAsync-await issues that have been triaged during a working group meeting.D-newcomer-roadblockDiagnostics: Confusing error or lint; hard to understand for new users.D-terseDiagnostics: An error or lint that doesn't give enough information about the problem at hand.T-compilerRelevant to the compiler team, which will review and decide on the PR/issue.

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions