Chapter 23: Rust Data Structures
Rust Data Structures.
This is a very natural next step. In Rust, when people say “data structures”, they usually mean two slightly different things:
- The built-in / primitive compound types that are part of the language syntax itself (tuples, arrays, slices)
- The powerful collections from the standard library that almost every real program uses (Vec, HashMap, String, HashSet, BTreeMap, etc.)
Rust deliberately keeps the language core very small — it gives you only a few built-in data structures — but the standard library collections are excellent, very safe thanks to ownership & borrowing, and usually what people actually reach for.
Today I’ll explain both layers like your personal teacher sitting next to you: slowly, with lots of copy-paste examples you can run right now (cargo run), clear analogies (Hyderabad traffic, biryani ingredients, phone contacts), comparisons to Python/Java/C++, and practical patterns you’ll see in 2026 Rust code.
Part 1 — Built-in / Primitive Data Structures (Language Level)
These come with Rust syntax — no use or std:: needed.
A. Tuple — Fixed-size, mixed-type grouping
|
0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 |
fn main() { // Syntax: (T1, T2, T3, ...) let person: (String, u32, bool) = ( String::from("Webliance"), 25, true ); // Access by .0 .1 .2 ... println!("Name: {}", person.0); println!("Age: {}", person.1); // Destructuring — very clean & common let (name, age, is_learning) = person; println!("{} is {} years old and learning Rust: {}", name, age, is_learning); // Unit tuple () = "nothing" (like void in other languages) let empty = (); } |
- Fixed size at compile time
- Heterogeneous (different types ok)
- Use case: returning multiple values from functions, small fixed records
B. Array — Fixed-size, same-type, stack-allocated
|
0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 |
fn main() { // [T; N] — N must be known at compile time let weekdays: [&str; 7] = [ "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday", "Sunday" ]; println!("First day: {}", weekdays[0]); println!("Last day: {}", weekdays[6]); // All elements same value — very fast let buffer = [0u8; 1024]; // 1 KB of zeros on stack // Iterate safely for day in weekdays { println!("Today is {}", day); } // Length is part of type — compile-time check // let wrong: [i32; 5] = weekdays; // ERROR — different size } |
- No heap allocation → very fast
- Bounds checked at runtime (panic on out-of-range)
- Cannot grow/shrink → use Vec when you need dynamic size
C. Slice — Dynamic view / reference into array / Vec / String
|
0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 |
fn main() { let numbers = [10, 20, 30, 40, 50, 60]; // Full slice let full_slice: &[i32] = &numbers[..]; // Sub-slice let middle = &numbers[1..4]; // 20, 30, 40 println!("Middle slice: {:?}", middle); // Mutable slice let mut scores = [85, 92, 78]; let top_two = &mut scores[0..2]; top_two[0] += 5; // mutate original array println!("Updated scores: {:?}", scores); } |
- Type: &[T] or &mut [T]
- Zero-cost view — no copying
- Very common in function parameters (like &str is actually a UTF-8 guaranteed slice of [u8])
Part 2 — Standard Library Collections (What You Use 95% of the Time)
These live in std::collections or std::vec, std::string.
A. Vec<T> — Growable, resizable array (most common collection)
|
0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 |
fn main() { // Create let mut temperatures = Vec::new(); // empty let mut cities = vec!["Hyderabad", "Warangal", "Nizamabad"]; // Add / push temperatures.push(32); temperatures.push(35); temperatures.extend([28, 30, 33]); // add multiple // Access println!("First temp: {}", temperatures[0]); println!("Last city: {}", cities.last().unwrap_or("Unknown")); // Iterate & mutate for city in &mut cities { *city = format!("{} City", city); } println!("Cities now: {:?}", cities); // Remove cities.pop(); // remove last cities.remove(0); // remove at index } |
- Heap allocated → can grow
- Like Python list, Java ArrayList, C++ std::vector
B. HashMap<K, V> — Fast key-value lookup
|
0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 |
use std::collections::HashMap; fn main() { let mut population = HashMap::new(); population.insert(String::from("Hyderabad"), 10_000_000); population.insert("Warangal".to_string(), 800_000); // Get if let Some(pop) = population.get("Hyderabad") { println!("Population: {} million", pop / 1_000_000); } // Iterate (unordered) for (city, pop) in &population { println!("{} has {} people", city, pop); } // Entry API — very idiomatic *population.entry("Nizamabad".to_string()).or_insert(0) += 500_000; } |
C. HashSet<T> — Unique unordered elements
|
0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
use std::collections::HashSet; fn main() { let mut foods = HashSet::new(); foods.insert("Biryani"); foods.insert("Biryani"); // ignored — duplicate foods.insert("Haleem"); foods.insert("Irani Chai"); println!("Favorite foods: {:?}", foods); println!("Has Biryani? {}", foods.contains("Biryani")); } |
D. BTreeMap<K, V> — Sorted key-value (when order matters)
|
0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
use std::collections::BTreeMap; fn main() { let mut leaderboard = BTreeMap::new(); leaderboard.insert(95, "Webliance"); leaderboard.insert(82, "Friend1"); leaderboard.insert(98, "TopPlayer"); // Iterates in sorted order (by score) for (score, name) in &leaderboard { println!("{} → {}", score, name); } } |
Quick Comparison Table (2026 Style)
| Name | Growable? | Ordered? | Access By | Memory | Best For |
|---|---|---|---|---|---|
| [T; N] | No | Yes | Index | Stack | Fixed small data, performance critical |
| &[T] | No | Yes | Index | Borrow | Function params, views, zero-copy |
| Vec<T> | Yes | Yes | Index | Heap | Most dynamic lists / sequences |
| String | Yes | Yes | — | Heap | Mutable text |
| HashMap<K,V> | Yes | No | Key | Heap | Fast lookup / cache / config |
| BTreeMap<K,V> | Yes | Yes | Key | Heap | Sorted data, range queries, leaderboards |
| HashSet<T> | Yes | No | Contains | Heap | Uniqueness, membership checks |
Teacher Summary & Advice
In Rust you usually start with:
- Small fixed data → array [T; N] or tuple
- Dynamic list → Vec<T>
- Text → String + &str
- Key-value lookup → HashMap (fast) or BTreeMap (sorted)
- Unique items → HashSet
Rust makes all of these memory-safe thanks to ownership & borrowing — no null pointer exceptions, no use-after-free, no data races.
Practice challenge for today:
- Create Vec<i32> of last 7 days temperatures → compute average
- Make HashMap<String, u32> city → population → print sorted by population (hint: collect to vec & sort)
- Use HashSet to store favorite foods & check if “Biryani” is there
Want to go deeper next?
- Iterators + .map, .filter, .collect (huge in real Rust code)
- Custom structs & enums as data structures
- Ownership & borrowing inside collections (very important)
- Or common crates like smallvec, indexmap, arrayvec?
Just tell me — your Rust teacher from Hyderabad is right here! 🦀🚀
