Reasoning with Async Rust

Zainab Ali

https://kebab-ca.se/presentations.html

Concurrency examples

  • Serving HTTP requests

  • Querying a database

  • A game render and input loop

  • Event loops in embedded systems

“Crack then fry the eggs. Meanwhile, fry the bacon for five minutes, or until crispy.”

fn breakfast() {
    cook_eggs();
    fry_bacon();
}
fn cook_eggs() {
    crack_eggs();
    fry_eggs();
}
fn crack_eggs() {
    println!("Started cracking egg.");
    random_sleep();
    println!("Finished cracking egg.");
}
fn random_sleep() { ... }
fn fry_eggs() { ... } // Prints and sleeps
fn fry_bacon() { ... } // Prints and sleeps
Started cracking egg.
Finished cracking egg.
Started frying egg.
Finished frying egg.
Started frying bacon.
Finished frying bacon.
async fn breakfast() {
    cook_eggs().await;
    fry_bacon().await;
}
async fn cook_eggs() {
    crack_eggs().await;
    fry_eggs().await;
}
async fn crack_eggs() {
    println!("Started cracking egg.");
    random_sleep().await;
    println!("Finished cracking egg.");
}
async fn fry_eggs() { ... }
async fn fry_bacon() { ... }
async fn random_sleep() { ... }
Started cracking egg.
Finished cracking egg.
Started frying egg.
Finished frying egg.
Started frying bacon.
Finished frying bacon.

Meanwhile, fry the bacon.”

async fn breakfast() {
    join!(cook_eggs(), fry_bacon());
}
Started frying bacon.
Started cracking egg.
Finished cracking egg.
Started frying egg.
Finished frying bacon.
Finished frying egg.

“Fry bacon for five minutes, or until crispy.”

async fn fry_bacon() {
    println!("Started frying bacon.");
    // This is simplified
    select! {
        () = timer() => (),
        () = crisp_bacon() => ()
    };
    println!("Finished frying bacon.");
}

async fn timer() { ... }
async fn crisp_bacon() { ... }
Started frying bacon.
Started timer.
Started crisping bacon.
Finished timer.
Finished frying bacon.

“Cook the eggs using a spoon: use a spoon to crack them and then fry them. Meanwhile, use a spoon to fry the bacon.”

Sharing mutable state

async fn breakfast() {
  let mut spoon = find_spoon();
  join!(cook_eggs(&mut spoon), fry_bacon(&mut spoon))
}

async fn cook_eggs(spoon: &mut Spoon) { ... }
async fn fry_bacon(spoon: &mut Spoon) { ... }
type SpoonMutex = Arc<Mutex<Spoon>>;

async fn cook_eggs(spoon_mutex: SpoonMutex) {
   let spoon = spoon_mutex.lock().await;
   ...
   drop(spoon);
}

async fn fry_bacon(spoon_mutex: SpoonMutex) { ... }
async fn breakfast() {
  let spoon_mutex = find_spoon();
  join!(cook_eggs(spoon_mutex.clone()),
        fry_bacon(spoon_mutex))
}
Started cracking egg.
Finished cracking egg.
Started frying egg.
Finished frying egg.
Started frying bacon.
Finished frying bacon.

“Cook the eggs using a spoon: use a spoon to crack them and then fry them using a pan. Meanwhile, use a spoon and use a pan to fry the bacon.”

async fn cook_eggs(spoon_mutex: SpoonMutex,
                   pan_mutex: PanMutex) {
    let spoon = spoon_mutex.lock().await;
    crack_eggs().await;
    let pan = pan_mutex.lock().await;
    fry_eggs().await;
    drop(spoon);
    drop(pan);
}
async fn fry_bacon(spoon_mutex: SpoonMutex,
                   pan_mutex: PanMutex) {
    join!(spoon_mutex.lock(), pan_mutex.lock());
    ...
    drop(spoon);
    drop(pan);
}
Started cracking egg.
Finished cracking egg.
let (spoon, pan) = loop {
    let maybe_spoon = try_lock_spoon(&spoon_mutex).await;
    let maybe_pan = try_lock_pan(&pan_mutex).await;
    match (maybe_spoon, maybe_pan) {
        (Some(spoon), None) => drop(spoon),
        (None, Some(pan)) => drop(pan),
        (Some(spoon), Some(pan)) => break (spoon, pan),
        (None, None) => (),
    }
};
async fn fry_eggs(...) {
    let (spoon, pan) = loop {...}
    ...
}

async fn fry_bacon(...) {
    let (spoon, pan) = loop {...}
    ...
}
Started cracking egg.
Finished cracking egg.
type SpoonAndPanMutex = Arc<Mutex<(Spoon, Pan)>>;
async fn cook_eggs(mutex: SpoonAndPanMutex) {
    let spoon_and_pan = mutex.lock().await;
    ...
    drop(spoon_and_pan);
}
async fn fry_bacon(mutex: SpoonAndPanMutex) {
    let spoon_and_pan = mutex.lock().await;
    ...
    drop(spoon_and_pan);
}

Actors

let (sender, receiver) = channel::<Message>(42);
async fn chef_actor(mut receiver: Receiver<Message>) {
    let mut spoon = Spoon; // internal mutable state
    let mut pan = Pan;
    while let Some(msg) = receiver.next().await {
       ...
    }
}
match msg {
   // use the state
   CrackEggs => crack_eggs(&mut spoon).await,
   FryEggs => fry_eggs(&mut spoon, &mut pan).await,
   FryBacon => fry_bacon(&mut spoon, &mut pan).await,
  }
async fn crack_eggs(spoon: &mut Spoon) { .. }
async fn fry_eggs(spoon: &mut Spoon, pan: &mut Pan) { ... }
async fn fry_bacon(spoon: &mut Spoon, pan: &mut Pan) { ... }
async fn send_cook_eggs(mut sender: Sender<Message>) {
    sender.send(CrackEggs).await;
    sender.send(FryEggs).await;
}

async fn send_fry_bacon(mut sender: Sender<Message>) {
    sender.send(FryBacon).await;
}
async fn breakfast() {
    let (sender, receiver) = channel::<Message>(42);
    join!(
        chef_actor(receiver),
        send_cook_eggs(sender.clone()),
        send_fry_bacon(sender),
    );
}

“He fixes radios by thinking!”

— Surely You're Joking, Mr. Feynman!

Learn more

Thank you

Source code: github.com/zainab-ali/rustikon-2025

Questions