Rust
Completed
-
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
27: Vectors, Strings, and HashMaps
Wait, why are there two different types of strings?
This is usually the first wall people hit. In most languages, a string is just a string. In Rust, you've got String and &str. I like to think of it this way: String is a growable, owned bucket of characters on the heap. &str (a string slice) is just a window looking into some text that already exists somewhere else.
If you're hardcoding a label in your code, use &str. If you're taking user input or building a message dynamically, you need a String. Here is how you'll actually use them together:
let greeting_label = "Welcome, "; // This is a &str
let user_name = String::from("Alice"); // This is a String
// We combine them into a new String
let full_message = format!("{}{}", greeting_label, user_name);
println!("{}", full_message);
The key takeaway is ownership. A String owns its data, so when it goes out of scope, the memory is cleaned up. A &str is just borrowing it.
How do I update a value in a HashMap without it being a mess?
If you've come from Python or JS, you're used to just checking if a key exists and then updating it. In Rust, doing that with if map.contains_key(...) is clunky because you end up performing the lookup twice—once to check and once to insert.
You want the Entry API. It's a bit of "magic" that lets you find the spot in the map and decide what to do with it in one go. I use this constantly for things like counting word occurrences:
use std::collections::HashMap;
let mut counts = HashMap::new();
let text = "apple banana apple cherry banana apple";
for word in text.split_whitespace() {
// This says: "Find the entry for 'word'. If it's not there, put 0 in.
// Then, give me a mutable reference to whatever value is there."
let count = counts.entry(word).or_insert(0);
*count += 1;
}
println!("{:?}", counts); // {"apple": 3, "banana": 2, "cherry": 1}
The *count += 1 part looks weird because or_insert returns a mutable reference. We have to dereference it to change the actual number inside the map.
When should I use a Vector instead of a regular array?
Arrays in Rust [T; N] have a fixed size that must be known at compile time. They're great for things that never change, like the days of the week or the coordinates of a 3D point. But in the real world, your data size is usually dynamic.
That's where Vec<T> comes in. It's basically an array that can grow. If you're building a list of high scores for a game, you don't know if you'll have 5 scores or 5,000. A Vector handles the memory reallocation for you behind the scenes.
let mut high_scores = Vec::new();
high_scores.push(1200);
high_scores.push(1500);
high_scores.push(900);
// You can access them just like an array
println!("The top score is: {}", high_scores[1]);
Just a heads up: indexing with [i] will crash (panic) your program if the index is out of bounds. If you aren't 100% sure the element exists, use .get(i), which returns an Option.
📋 Practical Task
Exercise: Build a Simple Tag Cloud Generator
Your goal is to write a program that takes a raw string of tags (separated by commas) and produces a list of tags that appear more than once, sorted by their frequency.
Requirements:
- Create a
HashMapto count how many times each tag appears in a comma-separated string (e.g.,"rust,coding,rust,systems,coding,rust"). - Filter the results: only keep tags that appear 2 or more times.
- Push these "popular" tags into a
Vecof tuples(String, i32). - Print the final Vector.
Bonus challenge: Try to use .split(',') and .trim() to handle tags that might have accidental spaces around them (like "rust, coding, rust").
There are no comments for now.