I need help clearing up confusion with async-related lifetimes

⚓ Rust    📅 2025-12-30    👤 surdeus    👁️ 4      

surdeus

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).

4 posts - 3 participants

Read full topic

🏷️ Rust_feed