Sign in

Olivia 🦀

@iolivia.me
717 followers 76 following 996 posts

👩‍💻 staff eng 🦀 post a lot about rust and rust gamedev ✍️ wrote sokoban.iolivia.me 🔥 blog at iolivia.me 🧵 weekly #rustlang threads every Thursday - subscribe here forms.gle/Tcm7cAkLt4NF9ZCZ9

PostsRepliesMedia
Olivia 🦀 @iolivia.me · 13/11/2025
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!
110
Olivia 🦀 @iolivia.me · 13/11/2025
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.
110
Olivia 🦀 @iolivia.me · 13/11/2025
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.
110
Olivia 🦀 @iolivia.me · 13/11/2025
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.
100
Olivia 🦀 @iolivia.me · 13/11/2025
2️⃣ Send Trait What if we wanted to return a transcript object like this? This works!
110
Olivia 🦀 @iolivia.me · 13/11/2025
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.
110
Olivia 🦀 @iolivia.me · 13/11/2025
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.
120
Olivia 🦀 @iolivia.me · 13/11/2025
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.
120
Olivia 🦀 @iolivia.me · 13/11/2025
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
110
Olivia 🦀 @iolivia.me · 13/11/2025
🕰️ 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 🧵👇
1150
Olivia 🦀 @iolivia.me · 30/10/2025
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
110
Olivia 🦀 @iolivia.me · 30/10/2025
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
110
Olivia 🦀 @iolivia.me · 30/10/2025
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.
110
Olivia 🦀 @iolivia.me · 30/10/2025
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.
110
Olivia 🦀 @iolivia.me · 30/10/2025
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.
110
Olivia 🦀 @iolivia.me · 30/10/2025
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.
110
Olivia 🦀 @iolivia.me · 30/10/2025
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.
100
Olivia 🦀 @iolivia.me · 30/10/2025
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
120
Olivia 🦀 @iolivia.me · 30/10/2025
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
120
Olivia 🦀 @iolivia.me · 30/10/2025
⛔ 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 🧵👇
1182
Olivia 🦀 @iolivia.me · 10/10/2025
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.
100
Olivia 🦀 @iolivia.me · 10/10/2025
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.
100
Olivia 🦀 @iolivia.me · 10/10/2025
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.
100
Olivia 🦀 @iolivia.me · 10/10/2025
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.
100
Olivia 🦀 @iolivia.me · 10/10/2025
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.
100
Olivia 🦀 @iolivia.me · 10/10/2025
2️⃣ Writing to the file with fs::write We can also use fs::write to write to the file, which is a bit more concise for small files. the write call will both open the file, write to it, and then close the file. Pretty neat!
100
Olivia 🦀 @iolivia.me · 10/10/2025
1️⃣ Writing to the file with writeln! Let's improve the error handling and handle the Result and write some data to the file. Notice the use of `ref mut` to get a mutable reference to the file inside the match arm and that the writeln! macro also returns a Result which we handle with expect.
100
Olivia 🦀 @iolivia.me · 10/10/2025
0️⃣ Creating files Let's start by creating a file. You can see that File::create is used to create a file, if the file already exists it will be truncated. The file is opened in write-only mode and the API returns a result which makes error handling explicit.
100
Olivia 🦀 @iolivia.me · 10/10/2025
📁 File IO in #rustlang File system handling is a core building block of using a language effectively, and Rust makes it pretty ergonomic with the std::fs module. Let's take a look at how to use File IO APIs in Rust with a real-world example: a grade manager for students and courses 🧵👇
1100
Olivia 🦀 @iolivia.me · 02/10/2025
3️⃣ Rough performance benchmark We can prove the performance difference by creating a large waitlist and measuring the time taken to remove all students. We can see the VecDeque is about 10x faster in this example, although this will vary in general by the size of the waitlist and the number of ops
100
Olivia 🦀 @iolivia.me · 02/10/2025
If we run demonstrate again with this implementation, we get the same output as before, so we can guarantee correctness, but let's double check our assumptions on performance.
100
Olivia 🦀 @iolivia.me · 02/10/2025
This means all existing students will get shifted in memory as index 0 is removed. You can see this implementation in action and it works.
100
Olivia 🦀 @iolivia.me · 02/10/2025
Now let's implement this trait using a Vec as storage. The implementation is pretty simple, it uses a Vec as the backing data structure, for adding a student it simply calls push, and for removing a student it calls remove(0) to remove the first student in the list.
100
Olivia 🦀 @iolivia.me · 02/10/2025
0️⃣ Requirements We want to build a waitlist where we can add students to the end of the waitlist, and remove students from the front when they are getting admitted. Let's start with a trait that looks like this.
100
Olivia 🦀 @iolivia.me · 02/10/2025
🎢 Understanding VecDeque in #rustlang We've looked at Vec and HashMap in previous threads, but what about queue usecases? Let's build a waitlist functionality for our university database and figure out how to use VecDeque 🧵👇
193
Olivia 🦀 @iolivia.me · 18/09/2025
However, this can become difficult to maintain as it breaks some encapsulation, for example this allows anyone to change the name of the course which might not be what we intended. To improve this we can use methods to provide controlled access to fields instead of making them public.
100
Olivia 🦀 @iolivia.me · 18/09/2025
Let's fix it by making it pub as well.
100
Olivia 🦀 @iolivia.me · 18/09/2025
3️⃣ Field visibility Let's now try to print the name field of the Course. This doesn't work because fields are private by default too.
100
Olivia 🦀 @iolivia.me · 18/09/2025
2️⃣ Fix Course struct visibility Let's make Course public too. Now everything compiles, great!
100
Olivia 🦀 @iolivia.me · 18/09/2025
1️⃣ Fix course module visibility Let's make courses pub and fix the error. Now it works, but Course is still private, let's fix that as well.
100
Olivia 🦀 @iolivia.me · 18/09/2025
0️⃣ Example Let's start with an example that has two nested modules: university -> courses, and a struct Course inside courses. Rust makes everything (modules, functions and structs) private by default. So the first error we get in this example is that the courses module is private.
100
Olivia 🦀 @iolivia.me · 18/09/2025
🔒 Visibility in #rustlang As projects grow, there's more and more code to organise and maintain. Visibility rules help you create clean APIs, prevent accidental misuse of internal code, and make refactoring safer by clearly defining public interfaces. Let's learn more about it 🧵👇
1120
Olivia 🦀 @iolivia.me · 04/09/2025
#rustconf the talk about CI optimisations in the #rustlang compiler is super interesting!
160
Olivia 🦀 @iolivia.me · 04/09/2025
bringing down the whole application, especially when using an external function/library you don't control You can see here that even though we had a panic in the second closure, the program continued executing after the catch_unwind block, so we see the last message that the program is running
100
Olivia 🦀 @iolivia.me · 04/09/2025
4️⃣ Testing panics Rust actually provides functionality to unit test code that is expected to panic. You can use the #[should_panic] attribute on a test function to indicate that the test should pass if a panic occurs test_1 passes because we expect a panic and we get one, and test_2 fails
200
Olivia 🦀 @iolivia.me · 04/09/2025
3️⃣ What happens during a panic? By default, Rust unwinds the stack. This means it works its way back up the call stack, cleaning up the memory for every function in the call chain. It's safe but has some overhead. Alternatively, you can configure your program to abort on panic.
100
Olivia 🦀 @iolivia.me · 04/09/2025
But the safest version of this code would actually be something like this where we handle options gracefully and we do not panic, so generally you should avoid unwrap and expect unless you are sure that the value is not None or Err.
100
Olivia 🦀 @iolivia.me · 04/09/2025
We could also use expect if we wanted to provide a custom error message along with the panic.
100
Olivia 🦀 @iolivia.me · 04/09/2025
Another example is using the `unwrap` method on an `Option` or `Result` type that is `None` or `Err`. The safer option of the previous code is to avoid [] and instead use a get method, which returns an Option. But we can still cause a panic if we unwrap that option which in this case is None.
100
Olivia 🦀 @iolivia.me · 04/09/2025
Now let's look at an example of a panic that occurs automatically. This code will panic because we are trying to access an index that is out of bounds for the array.
100