Olivia 🦀
banner
iolivia.me
Olivia 🦀
@iolivia.me
👩‍💻 staff eng
🦀 post a lot about rust and rust gamedev
✍️ wrote https://sokoban.iolivia.me
🔥 blog at https://iolivia.me

🧵 weekly #rustlang threads every Thursday - subscribe here https://forms.gle/Tcm7cAkLt4NF9ZCZ9
That's it, we learned the basics of how async programming works in Rust!

Hope it helped you learn 1% more Rust today, follow me for more threads like this and subscribe to receive these threads over email forms.gle/vY6zXE21Dkwa... 🦋 🦀
Rust Threads Newsletter
Get bite-size Rust Threads about fundamental concepts and practical usage right in your inbox every Thursday. Checkout this for a sample of previous threads:…
forms.gle
November 13, 2025 at 8:05 AM
6️⃣ Conclusion
- Futures are lazy state machines
- Async provides concurrency, not necessarily parallelism
- Cooperative multitasking: tasks yield at .await points only
- Send = safe to move between threads
- Sync = safe to share references btwn threads
- Use async for I/O-bound, threads for CPU-bound
November 13, 2025 at 8:05 AM
5️⃣ When to Use Async vs Threads

Async is perfect for I/O-bound operations where you're waiting for external resources, use async for concurrency with I/O. Use threads for CPU parallelism!
November 13, 2025 at 8:05 AM
The runtime uses cooperative multitasking - it only switches between tasks at .await points. If a task never yields, it can block everything!
November 13, 2025 at 8:05 AM
4️⃣ The Runtime

Async code needs a runtime to execute. The runtime manages the state machines, decides when to poll futures, and handles task scheduling. A main function in itself can be sync, with a runtime inside, or you could use #[tokio::main] to achieve the same with less boilerplate.
November 13, 2025 at 8:05 AM
implementations for Send and Sync, and if they don't it's because most of the time they are not safe to send across threads. In practice I think you'll find most of the time in basic cases it will just work out of the box.
November 13, 2025 at 8:05 AM
3️⃣ Sync Trait

Sync trait is automatically implemented for types for which it is safe to share references between threads. A type cannot be Sync unless it is Send, which of course makes sense as you wouldn't be able to share a reference to an "unsafe" object.

Most types get automatic ...
November 13, 2025 at 8:05 AM
This is why Send trait is important because it prevents at compile time thread safety issues!
November 13, 2025 at 8:05 AM
But what if we used `Rc<String>` instead of String like this?

This won't work because Rc doesn't implement Send, which means BadTranscript doesn't implement Send, which means we actually can't move this value between threads and use it as an output of the async task.
November 13, 2025 at 8:05 AM
2️⃣ Send Trait

What if we wanted to return a transcript object like this?

This works!
November 13, 2025 at 8:05 AM
You can check out the Future trait, or more importantly the Poll enum which shows you exactly how this works under the hood. Basically each Poll is either Ready which means the future has finished running and we have the output T, or it's Pending which means it's still executing.
November 13, 2025 at 8:05 AM
1️⃣ Futures

When you write async fn, Rust creates a Future - a value that may not be ready now but will be later. Futures are lazy and do nothing until you await them.

Behind the scenes, Rust compiles your async function into a state machine that can be paused at each await and resumed later.
November 13, 2025 at 8:05 AM
3 seconds are real work, some of it is waiting for a network or IO call to come back.

This is a perfect example to use async to handle all transcript requests concurrently and optimise resources. While we wait for that, we can do other work from another task.
November 13, 2025 at 8:05 AM
We can expect the transcript generation under the hood does multiple steps like:
* call an API over the network to get the student's data
* do some CPU intensive work to calculate averages and totals based on that data
* generate a PDF in memory
* save PDF file

This means not all those ..
November 13, 2025 at 8:05 AM
0️⃣ Blocking vs Async Code

Let's see what happens when 3 students request transcripts using traditional blocking code. Not only is everything sequential, so each student needs to wait for all transcripts to finish before getting theirs, but also if we are on the main thread we'd be freezing the UI
November 13, 2025 at 8:05 AM
That's it, we looked at how to do basic error handling in rust and then improved it using anyhow for context and thiserror w error types.

Hope it helped you learn 1% more Rust today, follow me for more threads like this and subscribe to receive these threads over email forms.gle/vY6zXE21Dkwa... 🦋 🦀
Rust Threads Newsletter
Get bite-size Rust Threads about fundamental concepts and practical usage right in your inbox every Thursday. Checkout this for a sample of previous threads:…
forms.gle
October 30, 2025 at 8:05 AM
💡 This is the key difference: anyhow is great for applications where you just need good error messages, while thiserror is perfect for libraries where consumers need to handle errors programmatically.
October 30, 2025 at 8:05 AM
The error messages are now cleaner and more structured. With custom error types, library clients can:
- Match on specific error variants to handle different cases
- Access error fields programmatically (like student_id, course_id)
- Make informed decisions based on the error type
October 30, 2025 at 8:05 AM
Then, we change all instances of anyhow! to return the appropriate EnrollmentError variant, and change the function signatures to return Result<T, EnrollmentError>. Note that we can no longer use `.context()` since that's an anyhow feature, but the errors themselves need to be descriptive enough
October 30, 2025 at 8:05 AM
and we wanted to expose more specific error types that clients can match on and handle differently, we need custom error types. We can use the thiserror::Error derive macro to easily create a custom error enum with different variants for each error case.
October 30, 2025 at 8:05 AM
3️⃣ Custom error types

The course enrollment is a pretty representative complex example, and the solution with anyhow context is normally good enough for internal application code where you don't need to worry about exposing error types to external clients

However, if this was a library ...
October 30, 2025 at 8:05 AM
We could extend this to a more complex example like course enrollment with a deeper chain. Here's how the process_enrollment function uses context at multiple levels.

Notice how each layer of context provides additional info about where in the call stack the error occurred.
October 30, 2025 at 8:05 AM
You can see in the last error the top level error message is "Failed to add student with id 2" and the cause is "Name cannot be empty". This additional context is super helpful for complex chains of function calls.
October 30, 2025 at 8:05 AM