DEV Community

Cover image for Asynchronous Rust 🦀 - RustConf 2025 | Day 1
Darko Mesaroš ⛅️ for AWS

Posted on • Edited on

Asynchronous Rust 🦀 - RustConf 2025 | Day 1

tl;dr
I was at RustConf 2025, and attended a cool workshop on Rust asynchronous programming. You can check it out here.

This year, for the first time, I've attended RustConf. It's a time for me to meet the Rust community, learn some new skills, and check up on Ferris, and see how it's doing. Rust conf is an annual conference for all us Rustaceans, and this year it was held in Seattle, WA. A place I live in, so I could easily just pop in! 🥳

Okay, let's get into day 1 - Workshop/Async day

Workshop - Async Fundamentals

To start off the event, I attended the Rust Async Fundamentals workshop by Herbert Wolverson. This gave me an opportunity to learn all about building asynchronous software in Rust.

The workshop is really great and Herbert is an excellent teacher. What I enjoyed the most is how he broke down tokio and other macros. Basically he showed us all what is exactly happening behind the scenes when you just add #[tokio::main] above your main() function. This made me realize how much heavy lifting Rust is doing, all while still giving you all the control you need!

The workshop is actually public if you wanna take a crack at it yourself, but please give Herbert a follow and check out rest of his courses. The workshop can be found on GitHub.

But, what did I learn today? 🤔

Actor Model

Okay, so I need to talk about something really neat I've learned today - and yeah, long time Rustaceans may scoff at me for this. But the Actor Model is something that I am finding incredibly useful. So what is an Actor Model? Well it's a pattern in Software Development when building concurrent programming. Basically what you are doing is organizing code around independent actors that communicate with each other through passing messages (using stuff like mpsc). The entire actix framework is an Actor framework for Rust (the one I used the most is actix-web).

It integrates seamlessly with Rusts onwership and concurrency principles. Since Rust has no Garbage Collector, actors provide an elegant way to manage memory safety. Because actors communicate via shared channels, they do not share mutable states, meaning this could help you avoid data races and concurrency bugs. 🔥

Well, let me show you in the simplest way possible (I will add comments to the code so it makes sense in a single shot):

src/lib.rs

// List of commands that can be sent to the Actor.
pub enum SharedStateCommand {
    Increment,
    Get(tokio::sync::oneshot::Sender<u64>), // oneshot is key here as this is how the information is sent back - via this channel
}


// This now spawns an actor. This actor maintains the `counter` state. The way it processes commands is through the `Sender` it returns, and in a thread-safe manner.
pub async fn start() -> tokio::sync::mpsc::Sender<SharedStateCommand> {

    // Create our tx and rx channels with a buffer capacity of 32 messages
    let (tx, mut rx) = tokio::sync::mpsc::channel(32);

    // Initialize the counter
    let mut counter: u64 = 0;

    // Spawn the actor and move everythinng under it
    tokio::spawn(async move {

        // Process the commands being sent to it
        while let Some(cmd) = rx.recv().await {
            match cmd {
                SharedStateCommand::Increment => {
                    counter += 1;
                },
                SharedStateCommand::Get(resp_tx) => {
                    let _ = resp_tx.send(counter);
                }
            }
        }
    });

    tx

}

// Function that gets the current counter value by using `oneshot` to send a command and wait for the return via the same channel
pub async fn get_counter(sender: &tokio::sync::mpsc::Sender<SharedStateCommand>) -> u64 {
    let (resp_tx, resp_rx) = tokio::sync::oneshot::channel();
    let cmd = SharedStateCommand::Get(resp_tx);
    if sender.send(cmd).await.is_err() {
        return 0;
    }
    resp_rx.await.unwrap_or(0)
}

// This is a fire and forget function where it just tells the actor to increment the counter without waiting for the results
pub async fn increment(sender: &tokio::sync::mpsc::Sender<SharedStateCommand>) {
    let cmd = SharedStateCommand::Increment;
    let _ = sender.send(cmd).await; // we dont really care here
}
Enter fullscreen mode Exit fullscreen mode

Okay and then how do we use this library crate now? Well here is an example using the axum framework. More details in the comments below

use axum::{http::StatusCode, Extension, Json, Router};
use shared_state_actor::SharedStateCommand;
use tokio::sync::mpsc::Sender;

#[tokio::main]
async fn main() {
    // Start my actor here and get its handle - BTW I've imported my library here in Cargo.toml
    let my_actor = shared_state_actor::start().await;

    // Some Axum boilerplate as well as routes
    let app = Router::new()
        .route("/", axum::routing::get(|| async { "Hello, World!" }))
        .route("/json", axum::routing::get(hello_json))
        .layer(Extension(my_actor)); // THIS IS KEY - Add the actor here, so it's available to all routes

    // Start the axum server
    let listener = tokio::net::TcpListener::bind("127.0.0.1:3001")
        .await
        .unwrap();
    axum::serve(listener, app).await.unwrap();
}

#[derive(serde::Serialize, serde::Deserialize, Debug)]
struct HelloJson {
    message: String,
}

async fn hello_json(
    Extension(my_actor): Extension<Sender<SharedStateCommand>>, // Extract the layer here using Axum's extractors
) -> axum::Json<HelloJson> {
    // Here is where we bump up the counter in the actor
    shared_state_actor::increment_counter(&my_actor).await;

    // Here is where we get the current counter (it uses the oneshot channel for this)
    let new_total = shared_state_actor::get_counter(&my_actor).await;


    // Respond back
    let reply = HelloJson {
        message: format!("Counter: {}", new_total),
    };
    axum::Json(reply)
}
Enter fullscreen mode Exit fullscreen mode

This such a great way to handle concurrency fearlessly (tm). 🦀 Besides this, I've also learned about Cancel Safety and Select statements in Tokio.

Wrap up

Overall feelings of Day 1 - I love it! The fact that the content is extremely technical tickles my fancy. Even though this was an essentials workshop, I felt I learned so much about Async patterns in Rust. Most of my workshop...mates(?) were incredibly skilled engineers, so even listening to their questions made me learn so many new things.

I am looking forward to day 2 when I get to visit some shorter sessions and mingle with more Rust folks!

Stay tuned for part 2.

Top comments (0)