Search code examples
asynchronousrustrust-futures

Rust: future factory method results in compiler error: value requires that `'1` must outlive `'2`


I want to repeat a certain user-provided function multiple times in an async context. Thus, I thought about a non-async closure that produces futures which can be consumed/awaited.

A minimal reproducible example is the following:

let counter = Arc::new(AtomicU64::new(0));
let counter_closure = counter.clone();
let future_producer = move || async {
    counter_closure.fetch_add(1, Ordering::SeqCst);
};

but this results in this error

562 |           let future_producer = move || async {
    |  _______________________________-------_^
    | |                               |     |
    | |                               |     return type of closure `impl Future<Output = ()>` contains a lifetime `'2`
    | |                               lifetime `'1` represents this closure's body
563 | |             counter_closure.fetch_add(1, Ordering::SeqCst);
564 | |         };
    | |_________^ returning this value requires that `'1` must outlive `'2`

How can I solve this problem?


Solution

  • The problem is that the closure owns counter_closure but the futures returned by the closure only reference this value owned by the closure. The signature of closures doesn't have a way to express this. It's akin to the "owning iterator" problem -- like how the Iterator trait doesn't permit expression of the case where the iterator dispenses references tied to its own lifetime, the family of closure traits that can be invoked on a reference to a closure (Fn and FnMut) don't have a way to express that the value returned from the closure borrows from the closure itself.

    If the compiler permitted this to compile, it would be possible to create a use-after-free situation by dropping the closure while futures it has previously returned still exist. This would destroy counter_closure while it's still borrowed.

    You can fix this by cloning the closure-owned counter_closure on each invocation of the closure before the async block, and moving this clone into the future with async move:

    let counter = Arc::new(AtomicU64::new(0));
    let counter_closure = counter.clone();
    let future_producer = move || {
        let counter_inner = counter_closure.clone();
        async move {
            counter_inner.fetch_add(1, Ordering::SeqCst);
        }
    };
    

    This uncouples the lifetime of the future from the closure by giving the future its own Arc.