Search code examples
rust

Minimal code for block_on async block on Rust without dependencies


I made this simplest possible block executor, but it requires unsafe and I wasn't able to remove this.

use core::future::Future;
use core::pin::Pin;
use core::task::{Context, Poll, RawWaker, RawWakerVTable, Waker};

// Create a no-op waker
fn dummy_raw_waker() -> RawWaker {
    fn no_op_clone(_: *const ()) -> RawWaker {
        dummy_raw_waker()
    }

    fn no_op(_: *const ()) {}

    RawWaker::new(
        core::ptr::null(),
        &RawWakerVTable::new(no_op_clone, no_op, no_op, no_op),
    )
}

// no dependency blocking function for debugging only
pub fn debug_only_unsafe_block_on<F: Future>(mut future: F) -> F::Output {
    let waker = unsafe { Waker::from_raw(dummy_raw_waker()) };
    let mut context = Context::from_waker(&waker);

    // Pin the future on the stack
    let mut future = unsafe { Pin::new_unchecked(&mut future) };

    loop {
        match future.as_mut().poll(&mut context) {
            Poll::Ready(val) => return val,
            Poll::Pending => {
                // In a real system, you might yield to the executor or check other tasks,
                // but in this simple case, we just keep polling.
            }
        }
    }
}

Is there a way to remove the unsafe keyword? The goal is to use no more dependencies on my code.


Solution

  • You can implement the Wake trait manually and use pin macro to avoid unsafe code

    #![deny(unsafe_code)]
    
    use core::future::Future;
    use core::task::{Context, Poll};
    use std::pin::pin;
    use std::sync::Arc;
    use std::task::Wake;
    
    struct NoOpWaker;
    
    impl Wake for NoOpWaker {
        fn wake(self: Arc<Self>) {}
    }
    
    // no dependency blocking function for debugging only
    pub fn debug_only_safe_block_on<F: Future>(mut future: F) -> F::Output {
        let waker = Arc::new(NoOpWaker).into();
    
        let mut context = Context::from_waker(&waker);
    
        let mut future = pin!(future);
    
        loop {
            match future.as_mut().poll(&mut context) {
                Poll::Ready(val) => return val,
                Poll::Pending => {
                    // In a real system, you might yield to the executor or check other tasks,
                    // but in this simple case, we just keep polling.
                }
            }
        }
    }
    
    async fn foo() -> i32 {
        1
    }
    
    fn main() {
        let data = debug_only_safe_block_on(foo());
        assert_eq!(1, data);
    }