Aerial view of a multilayer highway interchange.

All Rust workshops

Async Rust: concurrency you can reason about

2 days Bookable for teams – on-site or remote

About this workshop

You are comfortable with Rust: ownership, traits, Arc, and what Send means. Async is the part you are less sure of, whether you have written an async fn and left it there or watched a Tokio service in production do something other than what you expected. This workshop answers the async questions that come after the syntax: who owns your state, what happens when a future is dropped halfway through, and what your server does when it is asked for more than it can deliver.

You will turn the example minidb, a small in-memory key-value store, into a networked service. By the end of the workshop, you will have added a length-prefixed protocol over TCP, handling several requests in flight per connection, an architecture where one task owns the data and everything else asks it, durability pluggable behind a trait, and tests that run the whole server on a simulated network. With that setup, a slow client cannot starve a fast one, an overloaded server says so instead of falling over, and a restart picks up where the last one left off.

The workshop is designed for developers who are comfortable writing Rust and want to stop guessing when it comes to async. If you are new to Rust instead, start with Learn Rust, starting from scratch.

Bookable for teams

2 days

On-site or remote

Book for your team

Syllabus

  1. The Tokio runtime

    We will cover what Tokio adds on top of the language, why a future does nothing until something polls it, and why .await is a suspension point rather than a call, along with as much of Future, poll, and Pin as reasoning about the runtime demands; a later chapter returns to them at the poll level.
  2. Tasks

    tokio::spawn turns a future into a task, which is not a thread and does not behave like one. We will cover the Send + 'static requirement, running work concurrently with join! and JoinSet, and moving work that cannot yield onto spawn_blocking so that it does not occupy the runtime's worker threads.
  3. Building the server

    You will turn minidb into a TCP server that speaks a line protocol, covering the accept loop, framing with BufReader and lines(), and one task per connection. We will also cover which errors should end a single connection and which should not reach the accept loop.
  4. Who owns the state

    Every connection is a task, and each of them needs access to the same store. You will build both answers, Arc<Mutex<Store>> and an actor task that owns the data outright, and we will cover which mutex to use and where its guard may not be held. The section ends with a criterion benchmark comparing the two approaches under contention.
  5. Cancellation

    In async Rust, cancelling work means dropping a future, which can happen at any suspension point. We will cover timeout, select!, and CancellationToken, the state a dropped future leaves behind, and cancel safety: why next_line can be dropped safely and a hand-written read loop cannot.
  6. Backpressure

    An unbounded queue grows until the process runs out of memory. We will cover bounded mpsc channels, the difference between waiting, refusing, and dropping work when a queue is full, and limiting concurrent connections with a Semaphore, where the point at which the permit is acquired determines what the limit protects.
  7. Shutdown and supervision

    Without a shutdown path, a deploy terminates requests that are still in flight. We will cover graceful shutdown with CancellationToken and TaskTracker, the ordering that drains connections before the store task, and how to decide what should happen when a task fails without being asked to stop.
  8. Testing async code

    We will cover Tokio's paused clock, which makes a thirty second timeout testable in microseconds, tokio::io::duplex for exercising a connection handler without opening a socket, and asserting on properties rather than on scheduling order. You will also instrument the server with tracing and test the resulting spans as structured data.
  9. Durability across restarts

    You will add a write-ahead log, recording every mutating request before it is applied, batching the syncs into a group commit so that a busy server does not make one trip to the disk per request, and replaying the log on startup so that a restart preserves the data.
  10. AsyncRead and AsyncWrite

    We will go underneath Tokio's IO, to the traits it is built on. We will cover the poll_read and poll_write contract as well as ReadBuf, and you will write an IO wrapper of your own that counts every byte passing through the server, dropping into the connection handler without changing a signature.
  11. Codecs and framing

    You will replace the hand-rolled line loop with a length-prefixed frame, implementing Decoder and Encoder and driving them with Framed. The same codec then carries the write-ahead log, and a restriction the old framing forced on values disappears. This also closes the loop on cancel safety: a partial frame lives in the codec's buffer, not on a dropped future's stack.
  12. Streams, sinks, and pipelining

    We will cover the Stream and Sink traits and splitting a connection into independently owned halves. You will then let one connection have several requests in flight at once, choosing the in-flight bound and keeping the responses in order deliberately rather than by accident.
  13. Async functions in traits

    You will make the store's durability pluggable behind a trait with async methods, and meet the compile error that made async fn in traits famous: the spawned actor demands Send futures the trait does not promise. We will cover impl Future in return position as the fix, and what dyn does and does not allow.
  14. Diagnosing a stuck runtime

    A blocked worker thread does not announce itself. You will find one with tokio-console, and we will cover the runtime's metrics as well as the bridges between sync and async code, block_in_place and Handle::block_on, and the ways they go wrong quietly.
  15. Testing against a hostile network

    You will run the whole server on a simulated network with turmoil: injected latency, dropped connections, and partitions, all deterministic and all inside a unit test, using the same abstractions that made the store pluggable.

Your mentor

Luca Palmieri

Principal Engineering Consultant

Luca Palmieri builds technology products for a living. His current focus is on backend development, software architecture and the Rust programming language. He is the author of "Zero to Production in Rust".

Who we are

Mainmatter has been active in the Rust ecosystem for years, creating the 100 Exercises to Learn Rust course, the C to Rust Migration Book organizing EuroRust, and contributing to open source.

We help teams adopt and build on Rust with confidence via trainings and workshops, hands-on team reinforcement, codebase assessments, and more.

We have trained developers at

Bookable for teams

2 days

On-site or remote

Book for your team

Can't find the right workshops for your team?

We are happy to tailor the curriculum to your needs. We can combine multiple workshops, pick and choose modules, as well as develop new content if desired.
Get in touch

Book this workshop

Our mentors look forward to working with your team and unlocking new capabilities.

Send us a message

Your message is being sent…

Unable to send message.

Please try again later or contact us at [email protected]

Thank you!

We will be in touch soon.

Async Rust: concurrency you can reason about