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
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
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
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
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
🕰️ Async Basics in #rustlang

Send, Sync, Future, tokio, Pin, I'm sure you've heard a lot of intimidating terminology when it comes to async Rust, so this thread is about explaining the basics in a simple way.

We'll build a transcript generator to understand async/await, Futures

🧵👇
November 13, 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
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
2️⃣ Anyhow context

Let's add new functionality that also validates the student name before adding them to the db. First, we need to import the Context trait and then use .context() to add context to the error returned from validate_name.
October 30, 2025 at 8:05 AM
1️⃣ Using Anyhow

Building on top of this, let's explore how anyhow can help improve our error handling from here. We first switch Result<Student, String> to anyhow::Result<Student> and use the anyhow::anyhow! macro to create errors with messages.
October 30, 2025 at 8:05 AM
Version 1 - Naive approach with panic-crashes the whole program on a simple error
Version 2 - Returning bool to indicate success/failure but no error details, we know what failed but not why
Version 3 - Using Result to return success values and error details, a good compromise and the rust default
October 30, 2025 at 8:05 AM
0️⃣ The evolution from panic to Result

Let's explore error handling approaches using a university database with Student and Course structs, and a Database holding students, courses and an enrollments HashMap.

Now let's explore 3 implementations for error handling:
V1 - Panic
V2 - Bool
V3 - Result
October 30, 2025 at 8:05 AM
⛔ Error handling in #rustlang with anyhow and thiserror

Error handling in Rust can be a complex topic, especially since a lot of the patterns are different from other languages.

Let's explore some basic options and then go deeper into using anyhow and thiserror crates

🧵👇
October 30, 2025 at 8:05 AM
7️⃣ Reading larger files

For larger files, it's better to use a buffered reader to avoid loading the entire file into memory. Here's how you can do that. Notice we use the BufReader and BufRead trait.
October 10, 2025 at 9:17 AM
6️⃣ Deleting a file

After we saw how to create, write, and read files, let's see how to delete a file, pretty simple! We could also use the equivalent for directories with fs::remove_dir or fs::remove_dir_all.
October 10, 2025 at 9:17 AM
5️⃣ Parsing contents into structured data

Technically not part of the std::fs API but another common pattern is to read a file and then parse its contents into a structured format. Here's how we can read back the grades file. We could also use JSON/CSV+real parser, this is just a simple example.
October 10, 2025 at 9:17 AM
4️⃣ Creating directories

Another common task is creating directories. You can use fs::create_dir_all to create a directory and all its parent components if they are missing.
October 10, 2025 at 9:17 AM
3️⃣ Reading to string

We can use fs::read_to_string to read the entire contents of a file into a string. This is super handy for small files. Again, we handle the Result to manage errors gracefully.
October 10, 2025 at 9:17 AM