Store async closure on struct in no_std

I've seen a few different help threads on storing a Box<Pin<...>> closure on a struct and I have a solution which works, however I'd like to make it work in no_std environments which is not something I've come across. no_std naturally means I lose the ability to have Box. Here's my code that works only in std environments which is a pretty standard affair:

use core::future::Future;
use core::pin::Pin;

struct Item;

// Why is Sync bound required?
type ReturnType<'any> = Pin<Box<dyn Future<Output = Result<(), ()>> + Sync + Send + 'any>>;

struct ItemCollection {
    items: Vec<Item>,
    // Why is Sync bound required?
    foo: Pin<Box<dyn for<'any> Fn(&'any Item) -> ReturnType<'any> + Sync + Send>>,
}

#[tokio::main]
async fn main() {
    let collection = ItemCollection {
        items: vec![Item, Item],
        foo: Box::pin(|_| {
            Box::pin(async {
                println!("Noice");

                Ok(())
            })
        }),
    };

    tokio::spawn(async move {
        let item: &Item = collection.items.get(0).unwrap();

        (collection.foo)(item).await.ok();
    })
    .await
    .ok();
}

(playground)

My two questions:

  1. How do I remove Box from ItemCollection::foo and ReturnType so it works in no_std? and
  2. I never want collection to be able to be shared across threads, so why do I need + Sync in a couple of places I noted above?

The stored closure could even be fn - I don't need to capture any other variables, but it must be possible to run this closure multiple times.

I can add more generics to ItemCollection if necessary, but it already has quite a large type signature so it would be good if I could avoid adding more.

There currently isn't a good answer for translating this to no_std. You're essentially running into the same problems as why we don't have async trait yet, and the solutions are essentially the same as they are for async traits. However, none of them are easy to implement, especially not on stable Rust.

As an aside, there are several things in your code snippet that are unnecessary:

  • Closures are not self-referential, so the Pin around the Box<dyn Fn> is unnecessary.
  • The Sync trait is pretty much always redundant on a future.

As for your question about why you need + Sync on the closure, well, you don't need it and your code will work if you do this:

let future = (collection.foo)(item);
future.await.ok();

It doesn't work in your code because calling the closure creates a temporary immutable reference to the closure, and temporaries always exist until the end of the expression, which is after the .await in this case. Thus, you're keeping an immutable reference alive until after the .await. Immutable references are Send only if the type itself is Sync.

Hi Alice,

Many thanks for your help once again :slight_smile:

I had a play around and came up with the following type. I'm pretty happy with this, at least until I can remove the Box<Pin<Future<...>>> when the right Rust features are stabilised:

type ReturnType<'any> = Pin<Box<dyn Future<Output = Result<(), ()>> + Send + 'any>>;

struct ItemCollection {
    items: Vec<Item>,
    foo: for<'any> fn(&'any Item) -> ReturnType<'any>,
}

I changed over to fn because I don't need to capture any variables which simplified things even more.