async_cell 0.1.0

A Cell<Option<T>> that you can await on.
Documentation

async_cell

The key type of this crate is AsyncCell which can be found in both thread-safe and single-threaded variants. It is intended as a useful async primitive which can replace more expensive channels in a fair number of cases.

AsyncCell<T> behaves a lot like a Cell<Option<T>> that you can await on.

For example, it can be used to author futures in a callbacky style:

use async_cell::sync::AsyncCell;

let cell = AsyncCell::shared();
let future = cell.take_shared();

std::thread::spawn(move || cell.set("Hello, World!"));

println!("{}", future.await);

In place of something like tokio::sync::watch to react to the latest value of a variable:

use async_cell::sync::AsyncCell;

// Allocate space for our counter.
let counter_to_print = AsyncCell::shared();

// Try to print out the counts as fast as we receive them.
let c = counter_to_print.clone();
spawn(async move {
     while let Some(count) = c.take().await {
         println!("Latest count: {}", count);
     }
});

// Begin counting!
for i in 0..1000 {
     counter_to_print.set(Some(i));
}
counter_to_print.set(None);

To juggle a Waker within more complex data structures:

use async_cell::unsync::AsyncCell;
use std::cell::RefCell;

// A simple channel for sending numbers.
struct MpscStack {
    ready: AsyncCell,
    list: RefCell<Vec<i32>>,
}

impl MpscStack {
    // Push a number to the end of the channel.
    fn push(&self, x: i32) {
        let mut list = self.list.borrow_mut();
        if list.is_empty() {
            self.ready.notify();
        }
        list.push(x);
    }

    // Pop a number off the end of the channel, blocking while it is empty.
    async fn pop(&self) -> i32 {
        loop {
            if let Some(x) = self.list.borrow_mut().pop() {
                return x;
            }

            self.ready.take().await;
        }
    }
}

Or can be used in place of a lazy_static + a oneshot channel to initialize some resource:

use async_cell::sync::AsyncCell;

// AsyncCell::new() is const!
static DATA: AsyncCell<String> = AsyncCell::new();

// Read the file on a background thread.
std::thread::spawn(|| {
    let hello = std::fs::read_to_string("tests/hello.txt").unwrap();
    DATA.set(hello);
});

// Do some work while waiting for the file.

// And ready!
assert_eq!(&DATA.take().await, "Hello, World!\n");

What can't async_cell do?

  • Be used to broadcast data. If you need multiple concurrent consumers, get yourself a channel.
  • Guarantee that multiple sent values are received. When a cell is set in a loop, the receiver might wake up only once, long after, and take the last.

Although this crate contains a number of utility functions, you should generally be able to make due with just AsyncCell::new, AsyncCell::set, and AsyncCell::take.