ispawn 0.0.2

Runtime-agnostic async spawners.
Documentation
//! A low-cost, `no_std` abstraction over `Future` spawners that erases the underlying spawn handle's
//! type. This allows writing libraries that spawn futures in an executor-agnostic way without
//! using viral type parameters throughout.
//!
//! ispawn's traits do not require an additional heap allocation per spawn (on top of the heap
//! allocation the underlying executor likely does). While spawning is typically a relatively
//! infrequent operation, and thus not particularly performance-sensitive, boxing a future before
//! spawning adds an extra pointer indirection and virtual dispatch every time the future is polled.
//!
//! Executors that want to support this optimization need to expose a way to split spawning into two
//! operations:
//! 1. allocate memory for task
//! 2. write future and queue to executor
//!
//! The first operation allocates without knowing the concrete type of the future - it's only given
//! the layout. Executors tend to wrap spawned futures in a task datastructure, with the future
//! potentially inline as a DST - the first step allocates this task datastructure but leaves the
//! future uninitialized.
//!
//! The second operation writes the future to the task structure and queues it toward the executor.
//!
//! This split allows the `spawn` call to be inlined such that the future's state can be written
//! directly to its final destination - hopefully avoiding a potentially large memcpy from the stack
//! to the heap.
//!
//! # Adding support for an executor
//!
//!

#![no_std]

#[cfg(feature = "alloc")]
extern crate alloc;

#[cfg(feature = "alloc")]
use alloc::rc::Rc;
use core::{
    alloc::Layout,
    future::Future,
};

pub type LocalSpawnDynPtr = *const (dyn LocalSpawnDyn + 'static);

pub struct LocalSpawner {
    spawner: LocalSpawnDynPtr,
    clone_fn: fn(spawner: LocalSpawnDynPtr),
    drop_fn: fn(spawner: LocalSpawnDynPtr),
}

impl LocalSpawner {
    #[cfg(feature = "alloc")]
    pub fn rc(inner: impl LocalSpawnDyn + 'static) -> Self {
        Self::from(Rc::new(inner) as Rc<dyn LocalSpawnDyn + 'static>)
    }

    pub fn spawn<F: Future<Output = ()> + 'static>(&self, f: F) {
        let spawner = unsafe { &*self.spawner };
        let spawn_completer = spawner.spawn_dyn(Layout::new::<F>());
        unsafe {
            spawn_completer.spawn(f);
        }
    }

    pub unsafe fn from_raw_parts(
        spawner: LocalSpawnDynPtr,
        clone_fn: fn(spawner: LocalSpawnDynPtr),
        drop_fn: fn(spawner: LocalSpawnDynPtr),
    ) -> Self {
        Self { spawner, clone_fn, drop_fn }
    }
}

impl Clone for LocalSpawner {
    fn clone(&self) -> Self {
        (self.clone_fn)(self.spawner);
        Self {
            spawner: self.spawner,
            clone_fn: self.clone_fn,
            drop_fn: self.drop_fn,
        }
    }
}

impl Drop for LocalSpawner {
    fn drop(&mut self) {
        (self.drop_fn)(self.spawner);
    }
}

pub trait LocalSpawnDyn {
    /// The `spawn` method must be called on the returned SpawnCompleter. If the `spawn_dyn`
    /// implementation allocates memory for the task (usually the case), not doing so will result
    /// in a memory leak.
    fn spawn_dyn(&self, future_layout: Layout) -> SpawnCompleter;

    unsafe fn finish_spawn(&self, task_ptr_as_dyn_future: *mut dyn Future<Output = ()>);
}

pub struct SpawnCompleter<'a> {
    pub spawner: &'a dyn LocalSpawnDyn,
    pub task_ptr: *mut (),
    pub future_ptr: *mut (),
}

impl SpawnCompleter<'_> {
    /// Safety: The caller must ensure that `F` has the same layout that was used to create this
    /// `SpawnCompleter`.
    pub unsafe fn spawn<F: Future<Output = ()> + 'static>(self, f: F) {
        core::ptr::write(self.future_ptr as *mut F, f);

        // Learned this trick from here:
        //   https://www.reddit.com/r/rust/comments/hcofkh/comment/fvgpv5e
        // This seems pretty dubious, but it works today.
        // TODO elaborate how this works
        self.spawner.finish_spawn(self.task_ptr as *mut F as *mut dyn Future<Output = ()>);
    }
}

#[cfg(feature = "alloc")]
impl From<Rc<dyn LocalSpawnDyn>> for LocalSpawner {
    fn from(spawner: Rc<dyn LocalSpawnDyn>) -> Self {
        unsafe {
            Self::from_raw_parts(
                Rc::into_raw(spawner),
                |spawner_ptr| Rc::increment_strong_count(spawner_ptr),
                |spawner_ptr| {
                    drop(Rc::from_raw(spawner_ptr));
                },
            )
        }
    }
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn it_works() {
        let result = add(2, 2);
        assert_eq!(result, 4);
    }
}