Simplest possible block_on?

Hi! As I'm learning about async Rust, I read Build your own block_on() by @stjepang, and Applied: Build an Executor from "Asynchronous Programming in Rust." There's quite a bit of nuance in even these didactic implementations. What I'd like to know is: what is the simplest possible implementation of block_on that will still drive any correct future to completion?

To make this easier, I'm thinking to add a few restrictions:

  • The implementation does not need to care at all about performance.
  • The implementation only needs to work in a single-threaded environment.
  • The implementation can assume it's the only executor on the system, if that helps.
  • The implementation doesn't need to support any kind of "spawn" functionality.

In this case I'm really only interested in a solution that's technically correct. The original motivation for this has been struggling to find a correct executor that will run on an embedded system.

Here’s my attempt from a few months ago. It should be correct as long as nothing else attempts to park or unpark the main thread.

Edit: I just cleaned this up to be more correct, simpler, and significantly less performant by ignoring the Waker and spin-waiting instead:


mod blocking_future {

    use std::future::*;
    use std::task::*;

    const PENDING_SLEEP_MS:u64 = 10;

    unsafe fn rwclone(_p: *const ()) -> RawWaker {
    unsafe fn rwwake(_p: *const ()) {}
    unsafe fn rwwakebyref(_p: *const ()) {}
    unsafe fn rwdrop(_p: *const ()) {}

    static VTABLE: RawWakerVTable = RawWakerVTable::new(rwclone, rwwake, rwwakebyref, rwdrop);

    fn make_raw_waker() -> RawWaker {
        static DATA: () = ();
        RawWaker::new(&DATA, &VTABLE)

    pub trait BlockingFuture: Future + Sized {
        fn block(self) -> <Self as Future>::Output {
            let mut boxed = Box::pin(self);
            let waker = unsafe { Waker::from_raw(make_raw_waker()) };
            let mut ctx = Context::from_waker(&waker);
            loop {
                match boxed.as_mut().poll(&mut ctx) {
                    Poll::Ready(x) => {
                        return x;
                    Poll::Pending => {

    impl<F:Future + Sized> BlockingFuture for F {}
1 Like

Awesome, thanks for posting this! I ended up coming up with basically the same solution, except I used this crate cooked-waker because I was scared of the unsafe code with RawWaker. My solution looks like this:

use std::task::{Context, Poll};
use std::future::Future;
use cooked_waker::{IntoWaker as _, WakeRef};

struct NoopWaker;

impl WakeRef for NoopWaker {
    fn wake_by_ref(&self) {
        // It's okay to do nothing only because block_on continually polls

fn block_on<F: Future>(future: F) -> F::Output {
    loop {
        if let Poll::Ready(output) = future.as_mut().poll(&mut Context::from_waker(&NoopWaker.into_waker())) {
            return output

N.B. for anyone who stumbles onto this thread: while it is simple, making the waker a no-op is not likely what you want, because continually polling a future is exactly what Rust's async ecosystem is designed to avoid. Not only does this require 100% CPU utilization while doing no work, but I think it could also cause a future to build up a really huge set of wakers, which could be trouble for memory consumption.


According to the spec, Futures are only supposed to notify the waker from their mos recent poll call. There’s no actual enforcement for that, though, and I don’t know how many real-world implmentations follow that part of the spec.

Oh, thanks for pointing that out! I did not know that. Also, I realized that we're always giving it the same waker, so as long as it's storing the waker in a set-type data structure I think it should be okay.