Skip to content

Unclear lifetime error in closure producing a future #74497

Open

Description

@95th

Code:

use std::future::Future;

pub async fn bar() {
    foo(|x| baz(x)).await;
}

pub async fn baz(x: &u8) -> bool {
    if *x == 1 {
        false
    } else {
        true
    }
}

pub async fn foo<F, T>(f: F) -> bool
where
    F: Fn(&u8) -> T,
    T: Future<Output = bool>,
{
    f(&32).await
}

produces following error:

error: lifetime may not live long enough
 --> src/lib.rs:4:13
  |
4 |     foo(|x| baz(x)).await;
  |          -- ^^^^^^ returning this value requires that `'1` must outlive `'2`
  |          ||
  |          |return type of closure is impl std::future::Future
  |          has type `&'1 u8`

Is this error correct? If yes, why?

following also doesn't work:

pub async fn bar() {
    foo(baz).await;
}

error (still not clear):

error[E0271]: type mismatch resolving `for<'r> <for<'_> fn(&u8) -> impl std::future::Future {baz} as std::ops::FnOnce<(&'r u8,)>>::Output == _`
  --> src/lib.rs:4:5
   |
4  |     foo(baz).await;
   |     ^^^ expected bound lifetime parameter, found concrete lifetime
...
15 | pub async fn foo<F, T>(f: F) -> bool
   |              --- required by a bound in this
16 | where
17 |     F: Fn(&u8) -> T,
   |                   - required by this bound in `foo`

meta:

$ rustc --version --verbose
rustc 1.45.0 (5c1f21c3b 2020-07-13)
binary: rustc
commit-hash: 5c1f21c3b82297671ad3ae1e8c942d2ca92e84f2
commit-date: 2020-07-13
host: x86_64-unknown-linux-gnu
release: 1.45.0
LLVM version: 10.0

Activity

  1. SNCPlay42 commented on Jul 19, 2020

    @SNCPlay42
    Contributor

    This note

    return type of closure is impl std::future::Future

    Is supposed to define the lifetime '2 referred to by the error message by showing it as a parameter of the return type, but '2 doesn't appear in the type anywhere, possibly because of the opaque impl Future type.

    cc #74072

  2. added
    A-diagnosticsArea: Messages for errors, warnings, and lints
    A-lifetimesArea: Lifetimes / regions
    C-bugCategory: This is a bug.
    T-compilerRelevant to the compiler team, which will review and decide on the PR/issue.
    on Jul 19, 2020
  3. nikomatsakis commented on Jul 21, 2020

    @nikomatsakis
    Contributor

    This code is invalid. The closure is given a &u8, which is only valid during the closure execution. But baz(x) captures x and embeds it into a future that is returned -- this future, when awaited, will (or could) attempt to use x, even though the closure itself has returned, and hence x is no longer valid.

    The error message here is obviously not great. We have specialized errors for these kinds of cases in closures normally, I'm not sure why it didn't trigger in this case.

  4. added
    AsyncAwait-TriagedAsync-await issues that have been triaged during a working group meeting.
    on Jul 21, 2020
  5. nikomatsakis commented on Jul 21, 2020

    @nikomatsakis
    Contributor

    There isn't a great workaround that uses stable syntax to make this compile, interestingly. We discussed it some on Zulip.

    The error message for a comparable scenario without async isn't that great either, actually, I am somewhat surprised (playground):

    error: lifetime may not live long enough
     --> src/lib.rs:4:13
      |
    4 |     foo(|x| baz(x))
      |          -- ^^^^^^ returning this value requires that `'1` must outlive `'2`
      |          ||
      |          |return type of closure is &'2 u8
      |          has type `&'1 u8`
    
  6. nikomatsakis commented on Jul 21, 2020

    @nikomatsakis
    Contributor

    Returning a reference to a local variable is somewhat better (playground):

    error[E0515]: cannot return value referencing local variable `p`
     --> src/lib.rs:4:26
      |
    4 |     foo(|x| { let p = 3; baz(&p) })
      |                          ^^^^--^
      |                          |   |
      |                          |   `p` is borrowed here
      |                          returns a value referencing data owned by the current function
    
  7. added a commit that references this issue on Jul 24, 2020
  8. added a commit that references this issue on Jul 24, 2020
  9. added a commit that references this issue on Nov 9, 2020
  10. WinLinux1028 commented on May 27, 2021

    @WinLinux1028

    This code is invalid. The closure is given a &u8, which is only valid during the closure execution. But baz(x) captures x and embeds it into a future that is returned -- this future, when awaited, will (or could) attempt to use x, even though the closure itself has returned, and hence x is no longer valid.

    The error message here is obviously not great. We have specialized errors for these kinds of cases in closures normally, I'm not sure why it didn't trigger in this case.

    If the Future which returned by f(&32) awaited out of &32's lifetime, I think so too.
    But if not, this code is valid.

    This code will work:

    pub async fn bar() {
        println!("{:?}",foo(|x| baz(x)).await);
    }
    
    pub async fn baz(x: &u8) -> bool {
        if *x == 1 {
            false
        } else {
            true
        }
    }
    
    pub async fn foo<F, T>(f: F) -> bool
    where
        F: Fn(&'static u8) -> T,
        T: Future<Output = bool>,
    {
        f(force_static(&32)).await
    }
    
    fn force_static<T>(a: &T) -> &'static T {
        unsafe { &*(a as *const T) }
    }
    
  11. eholk commented on Sep 8, 2021

    @eholk
    Contributor

    One of the things I find surprising here is when the error messages talk about '1 and '2. It's not really clear where these lifetimes would come from, although presumably they are some implied or inferred lifetime that's not written explicitly. Would it be possible to have rustc give some more information about where these come from, such as what values are borrowed with each lifetime?

  12. 13 remaining items

  13. moved this from On deck to Claimed in wg-async workon Mar 2, 2023
  14. moved this from Claimed to In progress (current sprint) in wg-async workon Mar 2, 2023
  15. vincenzopalazzo commented on May 4, 2023

    @vincenzopalazzo
    Member

    Okay, after spending some time on it, I've come up with a summary.

    This is the complete example that includes the error, and also explains why this code will not work.

    use std::future::Future;
    
    pub async fn bar() {
        foo(|x| baz(x)).await;
    }
    
    pub async fn baz(x: &u8) -> bool {
        if *x == 1 {
            false
        } else {
            true
        }
    }
    
    /// From Nico
    ///
    /// This code is invalid. The closure is given a &u8, which is only valid
    /// during the closure execution. But baz(x) captures x and embeds it
    /// into a future that is returned -- this future, when awaited, will
    /// (or could) attempt to use x, even though the closure itself has
    /// returned, and hence x is no longer valid.
    ///
    /// The error message here is obviously not great. We have specialized
    /// errors for these kinds of cases in closures normally, I'm not sure
    /// why it didn't trigger in this case.
    pub async fn foo<F, T>(f: F) -> bool
    where
        F: Fn(&u8) -> T,
        T: Future<Output = bool>,
    {
        f(&32).await
    }
    
    fn main() {}

    But it seems that, upon further reflection, the point made by @WinLinux1028 and analyzed by @eholk is correct. I wasn't able to find a counterexample where this code doesn't work. However, I do need to say that the solution proposed by @estebank is more attractive to me because it exactly describes what the callback does (since I don't like using 'static lifetimes).

    Here is the revised code:

    use std::future::Future;
    
    pub async fn bar() {
        foo(|x| baz(x)).await;
    }
    
    pub async fn baz(x: &u8) -> bool {
        if *x == 1 {
            false
        } else {
            true
        }
    }
    
    pub async fn foo<'a, F, T>(f: F) -> bool
    where
        F: Fn(&'a u8) -> T,
        T: Future<Output = bool>,
    {
        f(&32).await
    }
    
    fn main() {}

    Furthermore, both synchronous and asynchronous code produce the same error. Therefore, I believe that this issue should be addressed for both types of code, rather than just the asynchronous function.

    As a result, I intend to propose this matter for discussion at the upcoming async triage meeting, in order to receive feedback on the solution suggested by @eholk for inclusion in the compiler.

    I did not check in deep, but this looks like another instance #102201

    @rustbot label -AsyncAwait-Triaged +I-async-nominated

  16. added
    I-async-nominatedNominated for discussion during an async working group meeting.
    and removed
    AsyncAwait-TriagedAsync-await issues that have been triaged during a working group meeting.
    on May 4, 2023
  17. moved this from In progress (current sprint) to In Progress (long term) in wg-async workon Sep 23, 2023
  18. moved this from In Progress (long term) to In progress (current sprint) in wg-async workon Sep 23, 2023
  19. eholk commented on Sep 25, 2023

    @eholk
    Contributor

    We revisited this in @rust-lang/wg-async triage today. We think the behavior of the compiler is correct, but there's room to improve the error message. We'll spend some more time iterating on what an improved error message would look like.

  20. added
    AsyncAwait-TriagedAsync-await issues that have been triaged during a working group meeting.
    and removed
    I-async-nominatedNominated for discussion during an async working group meeting.
    on Sep 25, 2023
  21. removed their assignment
    on Sep 25, 2023
  22. traviscross commented on Sep 25, 2023

    @traviscross
    Contributor

    Here's the minimal reproduction of this issue:

    fn foo<F: Fn(&()) -> T, T>(f: F) {
        f(&());
    }
    
    // error: lifetime may not live long enough
    fn test() {
        foo(|x: &()| x);
        //      -  - ^ returning this value requires that `'1` must outlive `'2`
        //      |  |
        //      |  return type of closure is &'2 ()
        //      let's call the lifetime of this reference `'1`
    }

    Playground link

    (Thanks to @compiler-errors.)

  23. timvermeulen commented on Aug 17, 2024

    @timvermeulen
    Contributor

    This should point at the bounds in foo and potentially suggest

    pub async fn foo<'a, F, T>(f: F) -> bool
    where
        F: Fn(&'a u8) -> T,
        T: Future<Output = bool> + 'a,

    This restricts foo to only call f with references that are valid for any arbitrary lifetime 'a, would that ever actually be useful? I expect this pattern to mostly arise when the function needs to call the closure with a reference to a local variable.

  24. Dylan-DPC commented on Nov 14, 2025

    @Dylan-DPC
    Member

    Current error:

    error: lifetime may not live long enough
     --> src/lib.rs:4:13
      |
    4 |     foo(|x| baz(x)).await;
      |          -- ^^^^^^ returning this value requires that `'1` must outlive `'2`
      |          ||
      |          |return type of closure `impl Future<Output = bool>` contains a lifetime `'2`
      |          has type `&'1 u8`
    
    error: could not compile `playground` (lib) due to 1 previous error
    
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Labels

A-async-awaitArea: Async & AwaitA-diagnosticsArea: Messages for errors, warnings, and lintsA-lifetimesArea: Lifetimes / regionsAsyncAwait-PolishAsync-await issues that are part of the "polish" areaAsyncAwait-TriagedAsync-await issues that have been triaged during a working group meeting.C-bugCategory: This is a bug.T-compilerRelevant to the compiler team, which will review and decide on the PR/issue.

Type

No type

Projects

  • Status
    In progress (current sprint)

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions