-
Section 1: Getting Started
-
Section 2: Core Syntax
-
Section 3: Ownership and Borrowing
-
Section 4: Structuring Data
-
Section 5: Collections and Error Handling
-
Section 6: Traits and Generics
-
Section 7: Concurrency
-
Section 8: Building for the Web
-
Section 9: Memory and Performance
-
Section 10: More Standard Library and Ecosystem
-
Section 11: Advanced Rust
-
Section 12: Rust for Systems and WebAssembly
-
Section 13: Tooling and Best Practices
-
Section 14: Data Structures and Algorithms in Rust
-
Section 15: Practical Projects
-
Section 16: Interview Practice
-
Section 17: std::collections In Depth
-
Section 18: std::io and std::fs In Depth
-
Section 19: std::net
-
Section 20: std::option and std::result In Depth
-
Section 21: std::iter In Depth
-
Section 22: std::sync In Depth
-
Section 23: std::string and std::str
-
Section 24: Cargo and Crates.io Ecosystem
-
Section 25: Popular Crates Ecosystem
-
Section 26: Rust Memory Model Deep Dive
-
Section 27: More Practice Exercises
-
Section 28: More Interview Practice
-
Section 29: Async Rust Deep Dive
-
Section 30: Tokio Ecosystem In Depth
-
Section 31: Error Handling Ecosystem Deep Dive
-
Section 32: Serde In Depth
-
Section 33: Web Development with Rust Deep Dive
-
Section 34: Database Access Ecosystem
-
Section 35: Rust for Embedded Systems Deep Dive
-
Section 36: Rust Macros In Depth
-
Section 37: Advanced Trait System
-
Section 38: Unsafe Rust In Depth
-
Section 39: Rust CLI Development
-
Section 40: Testing Ecosystem Deep Dive
-
Section 41: WebAssembly Deep Dive
-
Section 42: Rust Design Patterns
-
Section 43: More Data Structures in Rust
-
Section 44: Final Practice Projects
-
Section 45: Rust Performance Optimization
-
Section 46: Rust Ecosystem Tooling
-
Section 47: More Interview and Review
20: Practice Exercise: Fixing Common Borrow Checker Errors
I remember the first time I hit a wall with the borrow checker. I felt like I was arguing with a compiler that was just being pedantic for the sake of it. But once you realize the borrow checker isn't trying to stop you from writing code—it's trying to stop you from crashing your program in production—the perspective shifts. Let's look at a piece of code that looks perfectly reasonable in C# or Java, but will make Rust throw a fit.
struct Project {
name: String,
tasks: Vec,
}
fn add_task_to_first_project(projects: &mut Vec, task: String) {
// We grab a reference to the first project to update it
let first = &projects[0];
// Now we want to add a new empty project to the list for some reason
projects.push(Project {
name: "New Project".to_string(),
tasks: vec![]
});
// Now we try to use that first reference again
first.tasks.push(task);
}
If you try to compile this, the compiler will scream at you. It'll tell you that you can't borrow projects as mutable because it's already borrowed as immutable. You might think, "Why? I'm just adding a new project to the end of the list; it shouldn't affect the first one."
The Memory Shift Trap
Here is what's actually happening under the hood. When you call projects.push(...), the Vec might run out of capacity. When that happens, Rust allocates a larger chunk of memory elsewhere and moves all the existing elements to that new location.
If Rust allowed the code above, the first variable would be pointing to the old memory location—memory that has just been freed. This is a "use-after-free" bug, and it's exactly the kind of memory corruption that leads to security vulnerabilities. The borrow checker isn't being annoying; it's saving you from a segfault that would have been a nightmare to debug in a larger system.
Solving the Conflict with Indexing
To fix this, we need to make sure we aren't holding a reference to the interior of the Vec while we are mutating the Vec itself. The simplest way to do this is to avoid storing the reference in a variable entirely. Instead, we perform our mutation on the Vec first, and then access the element we need by its index right when we need it.
fn add_task_to_first_project(projects: &mut Vec, task: String) {
// Move the mutation of the container to the top
projects.push(Project {
name: "New Project".to_string(),
tasks: vec![]
});
// Access the element by index ONLY when we are ready to use it.
// This borrow starts and ends right here.
projects[0].tasks.push(task);
}
By doing this, the immutable borrow of projects[0] doesn't exist while projects.push is happening. The borrow checker is happy because there is no overlap. I've found that whenever I see this error, the first thing I ask myself is: "Am I holding onto a reference longer than I actually need to?" Usually, the answer is yes.
📋 Practical Task
Exercise: Fixing the Inventory Manager
You are building a simple inventory system. You have a Warehouse struct that contains a list of Items. You've written a function that is supposed to find a specific item by its ID and "archive" it by moving it into a separate archived_items list. However, the code currently fails to compile because of a borrow checker error.
Your Goal: Modify the archive_item function so that it compiles and runs correctly. You must ensure that you are not holding a reference to an item in the inventory list while simultaneously trying to mutate that list (by removing the item).
struct Item {
id: u32,
name: String,
}
struct Warehouse {
inventory: Vec,
archived_items: Vec,
}
impl Warehouse {
fn archive_item(&mut self, item_id: u32) {
// BUGGY CODE:
// This currently fails because 'target' borrows self.inventory,
// and then self.inventory.remove() tries to borrow it mutably.
let target = self.inventory.iter().find(|i| i.id == item_id);
if let Some(item) = target {
// We need to find the index of this item to remove it
let index = self.inventory.iter().position(|i| i.id == item_id).unwrap();
// This line causes the borrow checker error
let removed_item = self.inventory.remove(index);
self.archived_items.push(removed_item);
}
}
}
fn main() {
let mut wh = Warehouse {
inventory: vec![
Item { id: 1, name: "Wrench".to_string() },
Item { id: 2, name: "Hammer".to_string() },
],
archived_items: vec![],
};
wh.archive_item(1);
println!("Archived items count: {}", wh.archived_items.len());
}There are no comments for now.