I need help clearing up confusion with async-related lifetimes

Hello fellow Rustaceans,

When compiling the following code:

#![allow(dead_code)]
#![allow(unused_variables)]
#![allow(unused_imports)]
use futures::Future;
use tokio::sync::mpsc::Receiver;

type Event = ();

trait EventHandler<'a>: FnMut(&'a Event) -> Self::Fut {
    type Fut: Future<Output = ()>;
}

impl<'a, F, Fut> EventHandler<'a> for F
where
    F: FnMut(&'a Event) -> Fut,
    Fut: Future<Output = ()> + 'a,
{
    type Fut = Fut;
}

async fn process_event<'a>(
    mut events: Receiver<Event>,
    handlers: &mut [impl EventHandler<'a>],
) -> Option<()> {
    match events.recv().await {
        Some(event) => {
            for event_handler in handlers {
                event_handler(&event).await;
            }
            Some(())
        }
        None => None,
    }
}

I get the following error:

error[E0597]: `event` does not live long enough
   --> src/lib.rs:28:31
    |
 21 | async fn process_event<'a>(
    |                        -- lifetime `'a` defined here
...
 26 |         Some(event) => {
    |              ----- binding `event` declared here
 27 |             for event_handler in handlers {
 28 |                 event_handler(&event).await;
    |                 --------------^^^^^^-
    |                 |             |
    |                 |             borrowed value does not live long enough
    |                 argument requires that `event` is borrowed for `'a`
...
 31 |         }
    |         - `event` dropped here while still borrowed
    |
note: requirement that the value outlives `'a` introduced here
   --> /playground/.rustup/toolchains/stable-x86_64-unknown-linux-gnu/lib/rustlib/src/rust/library/core/src/ops/function.rs:163:37
    |
163 | pub const trait FnMut<Args: Tuple>: FnOnce<Args> {
    |                                     ^^^^^^^^^^^^

From what I understand about the sugar-coating of async/await, the event_handler(&event).await somewhat create a Future (the event_handler() part), then polls it (the .await part), and returns immediately if the poll returns Pending.

I'm unsure whether the magic that translates the async function to a state-machine takes care of internal lifetimes, but it looks like it doesn't.

From what I understand, the error here is that event_handler() returns a Future that (correctly, I believe) has 'a as a lifetime. And it looks like the compiler assumes the .await will get out of the scope of the Some(event) pattern, hence the complaint about event not living long-enough.

But reading this code literally, and ignoring the internals of the generated state-machine, the future never moves outside of the scope of the Some(event) pattern, thus the future should never outlive event.

What's wrong in my reasoning?

PS: This is not a XY problem because I want to understand what's technically wrong here, although I'm also interested in what should be the right pattern when trying to call back to a sequence of generic handlers (it looks like the mutable slice of FnMut(&Event) -> Future<Output = ()> won't cut it).

1 Like

this error has nothing to do with async. trying to focus on async will only lead to confusion.

here is a simplified version of the code that shows the same error, with 0 async/future reference

#![allow(dead_code)]
#![allow(unused_variables)]
#![allow(unused_imports)]

type Event = ();

trait EventHandler<'a>: FnMut(&'a Event) -> Self::Fut {
    type Fut;
}

fn process_event<'a>(
    events: Option<Event>,
    handlers: &mut [impl EventHandler<'a>],
) -> Option<()> {
    match events {
        Some(event) => {
            for event_handler in handlers {
                _ = event_handler(&event);
            }
            Some(())
        }
        None => None,
    }
}

the error is due to calling a function which only accepts long lived references with a short lived reference.

the simples fix is

handlers: &mut [impl for<'a> EventHandler<'a>],

the reason behind your error is simple. an EventHandler<'a> can only accept a &'a Event.
'a is a lifetime defined on the function, thus it does outlive the function.
so these EventHandlers can only accept references that are still valid after the function has ended, and that all have the same lifetime('a).
the reference you pass does not last until after the function has ended, therefore this cannot work as is

1 Like

if you are talking about the self referential and pinning mechanism, the async/await sugar does take care of that.

but this error is not about self-referential lifetime. generic lifetime parameter is chosen at the callsite, which can not be unified with local borrows inside the function. depending on your use case, you may be able to use a higher ranked trait bound here:

-async fn process_event<'a>(
+async fn process_event(
     mut events: Receiver<Event>,
-    handlers: &mut [impl EventHandler<'a>],
+    handlers: &mut [impl for <'a> EventHandler<'a>],
 ) -> Option<()> {
   //...
 }