Storing async fn?

So I'm trying to figure out how to store async fns (I tried to implement a spawn() function on my executor but I can't figure out how to do it from the way its designed, and I'm working in a no_std environment and I don't have threads). I've tried this type alias:

type InitFunc = Box<dyn Fn(PciDevice) -> Pin<Box<Pin<dyn Future<Output = ()>>>>+Send+Sync>;

However, Rust complains that (dyn Future<Output = ()> + 'static)'s size isn't known when I try to store this in a HashMap. My executor, as its currently written, defines futures as Pin<Box<dyn Future<Output = ()>>>. If I redefine my type alias as Pin<Box<dyn Future<Output = ()>+Send+Sync>>, Rust first tells me that &Pin<alloc::boxed::Box<dyn Future<Output = ()> + Send + Sync>> isn't a function, even if dereference it, then it goes on to say that fn(PciDevice) -> impl Future {nvme::init} is not a future. So I'm horribly confused (I do have the init() function defined as async...). For reference I'm trying to store it in a hash map:

lazy_static! {
    static ref PCI_DEVICES: RwLock<MiniVec<PciDevice>> = RwLock::new(MiniVec::new());
    static ref DEV_INIT_FUNCS: RwLock<HashMap<DeviceIdentification, InitFunc>> = RwLock::new({
        let mut map: HashMap<DeviceIdentification, InitFunc> = HashMap::new();
        if cfg!(feature = "nvme") {
            use minivec::mini_vec;
            let dinfo = mini_vec![(0x01, 0x08, 0x02), (0x01, 0x08, 0x03)];
            map.insert(dinfo, Box::pin(crate::nvme::init));
        }
        map
    });
}

So I can then call it later:

                            let funcs = DEV_INIT_FUNCS.read();
                            for (k, v) in funcs.iter() {
                            let devs: MiniVec<&DeviceInformation> = k.iter().filter(|dinfo| read_byte(addr as usize, DEV_CLASS) == dinfo.0 && read_byte(addr as usize, DEV_SUBCLASS) == dinfo.1 && read_byte(addr as usize, PROG_IF) == dinfo.2).collect();
                            if !devs.is_empty() {
                            info!("Found device driver for class={:X}, subclass={:X}, program interface={:X}; initializing", dev.class, dev.subclass, dev.prog_if);
                            *v(dev).await;
                            }
                            }

So I'm really not sure what's going wrong with this one.

You have an extra Pin. The Pin goes around the Box, not inside it.

Well it is not a function, it's a future. (well it would be if the & was taken off)

Well it is not a future. It's a function.

Okay, so I redefined the alias as Box<dyn Fn(PciDevice) -> Pin<Box<dyn Future<Output = ()>>>+Send+Sync>. Its now throwing this error:

error[E0271]: type mismatch resolving `<fn(PciDevice) -> impl Future {nvme::init} as FnOnce<(PciDevice,)>>::Output == Pin<alloc::boxed::Box<(dyn Future<Output = ()> + 'static)>>`
   --> libk\src\pci.rs:92:31
    |
92  |             map.insert(dinfo, Box::new(crate::nvme::init));
    |                               ^^^^^^^^^^^^^^^^^^^^^^^^^^^ expected struct `Pin`, found opaque type
    |
   ::: libk\src\nvme\mod.rs:739:35
    |
739 | pub async fn init(dev: PciDevice) {
    |                                   - the `Output` of this `async fn`'s found opaque type
    |
    = note:   expected struct `Pin<alloc::boxed::Box<(dyn Future<Output = ()> + 'static)>>`
            found opaque type `impl Future`
    = note: required for the cast to the object type `dyn Fn(PciDevice) -> Pin<alloc::boxed::Box<(dyn Future<Output = ()> + 'static)>> + Send + Sync`

The problem is that I can't just do Box<dyn Fn(PciDevice) -> Box<dyn Future<Output = ()>>+Send+Sync> because then Rust thinks I'm trying to unpin it -- which I get. So I'm not really sure how to store this. The design of my executor is over here (in that module and the cooperative submodule). The run() method is blocking, and right now I have no way that I know of of preventing that, and so I have no idea how to actually implement any kind of spawn implementation that would make this easier.

You can use Box::pin to convert a T into a Pin<Box<T>> directly.

Do you mean by using Box::pin instead of Box::new?
I feel like I'm overthinking this problem but this is just so confusing.

Yes, use Box::pin instead. It's just a shortcut for Pin::new(Box::new(value)), which is slightly longer to type.

I'm not really sure where to put that, because if I replace Box::new() with Box::pin() it doesn't actually solve anything.

Try this

Box::new(|device| Box::pin(crate::nvme::init(device)))

That won't work. What I'm trying to do is this: I want to be able to store the async function to be called later. When I store it in the HashMap I don't have the device information yet, just the function to be called. When I probe the PCIe bus, I gather the required information, and then I want to be able to gain a reader-writer lock to the list of async init functions, find the appropriate device, and then call that async function with the newly gathered device information.

You don't need to have a device already. That creates a closure that takes a device and passes it to the init function, and returns a Box<Pin<dyn Future>> that can be awaited to get the result. In other words, it's wrapping the init function which returns a concrete Future type in one that returns a dynamic Future type.

(Playground)

Here's an enum for an event in flo_draw that it uses to queue a function that spawns a future. I think this is roughly what you're looking for:

use futures::future::{LocalBoxFuture};
pub enum GlutinThreadEvent {
    /* ... */
    RunProcess(Box<dyn Send+FnOnce() -> LocalBoxFuture<'static, ()>>),
}

Or rewritten in the terms in your original post:

type InitFunc = Box<dyn Send+FnOnce(PciDevice) -> LocalBoxFuture<'static, ()>>

My requirements here are a bit different to yours: I'm spawning a future that runs only on a single thread from a different thread, so the function here is Send but the future is not, but hopefully you can see how to modify this to your own requirements.

Replace FnOnce with Fn if you want a generator that can spawn multiple futures - if not, FnOnce is easier to use. You can also replace LocalBoxFuture with BoxFuture if you want a future that is Send (they're shorthand for the pinning you're attempting here). The function looks something like this:

|| async { /* ... */ }.boxed_local()

(.boxed() if the future should be Send). LocalBoxFuture and BoxFuture can both be polled with poll_unpin() in an executor, so you don't need to deal with pinning them yourself.

This worked. Thank you!

Don't thank me! I only copied the code that @alice wrote. :slight_smile:

1 Like