Search code examples
asynchronousrustclosures

Type erasure of a closure that returns an async block


Consider the following closure that returns an async block:

|entity: &mut Entity| async move {
    entity.foo().await;
}

Is it possible to type erase and store this closure inside an enum, without specifying a generic type or lifetime on that enum? Consider the following MWE:

use std::future::Future;

struct Entity;
impl Entity {
    async fn foo(&mut self) {} 
}

fn main() {
    erase_and_store(|entity: &mut Entity| async move {
        entity.foo().await;
    });
}

fn erase_and_store<'a, C, F>(closure: C) -> Task where
    C: FnOnce(&'a mut Entity) -> F,
    F: Future<Output = ()> + 'a {
    
    Task::Variant(/* TODO convert closure to something that can be stored */)
}

enum Task {
    Variant(/* TODO store closure */)
}

I have tried a couple different approaches but it seems that even if I put everything behind boxed trait objects, I can not stop this generic lifetime 'a from leaking through to my Task enum.

type AnyFuture<'a> = Box<dyn Future<Output = ()> + 'a>;
type AnyClosure<'a> = Box<dyn FnOnce(&'a mut Entity) -> AnyFuture<'a>>;

enum Task {
    Variant(AnyClosure) // requires <'a>
}

Solution

  • What you want is a higher-ranked lifetime:

    type AnyFuture<'a> = Pin<Box<dyn Future<Output = ()> + 'a>>;
    type AnyClosure = Box<dyn for<'a> FnOnce(&'a mut Entity) -> AnyFuture<'a>>;
    

    That can be elided:

    type AnyFuture<'a> = Pin<Box<dyn Future<Output = ()> + 'a>>;
    type AnyClosure = Box<dyn FnOnce(&mut Entity) -> AnyFuture<'_>>;