Beginner Level
Collections
Chapter 8: Collections π
Collections store multiple values in data structures allocated on the heap. Rust's standard library includes several useful collections!
Vectors
Creating Vectors
fn main() {
// Empty vector
let v: Vec<i32> = Vec::new();
// Using vec! macro
let v = vec![1, 2, 3];
// With capacity
let mut v = Vec::with_capacity(10);
println!("Vector: {:?}", v);
}Adding Elements
fn main() {
let mut v = Vec::new();
v.push(5);
v.push(6);
v.push(7);
v.push(8);
println!("{:?}", v);
}Reading Elements
fn main() {
let v = vec![1, 2, 3, 4, 5];
// Using indexing (panics if out of bounds)
let third: &i32 = &v[2];
println!("The third element is {}", third);
// Using get method (returns Option)
let third: Option<&i32> = v.get(2);
match third {
Some(third) => println!("The third element is {}", third),
None => println!("There is no third element."),
}
}Iterating Over Vectors
fn main() {
let v = vec![100, 32, 57];
// Immutable references
for i in &v {
println!("{}", i);
}
// Mutable references
let mut v = vec![100, 32, 57];
for i in &mut v {
*i += 50;
}
println!("{:?}", v);
}Storing Different Types with Enums
#[derive(Debug)]
enum SpreadsheetCell {
Int(i32),
Float(f64),
Text(String),
}
fn main() {
let row = vec![
SpreadsheetCell::Int(3),
SpreadsheetCell::Text(String::from("blue")),
SpreadsheetCell::Float(10.12),
];
for cell in &row {
println!("{:?}", cell);
}
}Strings
Creating Strings
fn main() {
// Empty string
let mut s = String::new();
// From string literal
let s = "initial contents".to_string();
let s = String::from("initial contents");
println!("{}", s);
}Updating Strings
fn main() {
let mut s = String::from("foo");
s.push_str("bar");
s.push('!');
println!("{}", s);
// Concatenation with +
let s1 = String::from("Hello, ");
let s2 = String::from("world!");
let s3 = s1 + &s2; // s1 is moved here
println!("{}", s3);
// Using format! macro
let s1 = String::from("tic");
let s2 = String::from("tac");
let s3 = String::from("toe");
let s = format!("{}-{}-{}", s1, s2, s3);
println!("{}", s);
}String Slicing
fn main() {
let hello = "ΠΠ΄ΡΠ°Π²ΡΡΠ²ΡΠΉΡΠ΅";
let s = &hello[0..4]; // Be careful with UTF-8!
println!("{}", s);
// Iterating over characters
for c in "ΠΠ΄".chars() {
println!("{}", c);
}
// Iterating over bytes
for b in "ΠΠ΄".bytes() {
println!("{}", b);
}
}Hash Maps
Creating Hash Maps
use std::collections::HashMap;
fn main() {
let mut scores = HashMap::new();
scores.insert(String::from("Blue"), 10);
scores.insert(String::from("Yellow"), 50);
println!("{:?}", scores);
// From vectors
let teams = vec![String::from("Blue"), String::from("Yellow")];
let initial_scores = vec![10, 50];
let mut scores: HashMap<_, _> =
teams.into_iter().zip(initial_scores.into_iter()).collect();
println!("{:?}", scores);
}Accessing Values
use std::collections::HashMap;
fn main() {
let mut scores = HashMap::new();
scores.insert(String::from("Blue"), 10);
scores.insert(String::from("Yellow"), 50);
let team_name = String::from("Blue");
let score = scores.get(&team_name).copied().unwrap_or(0);
println!("Score: {}", score);
// Iterating
for (key, value) in &scores {
println!("{}: {}", key, value);
}
}Updating Hash Maps
use std::collections::HashMap;
fn main() {
let mut scores = HashMap::new();
// Overwriting
scores.insert(String::from("Blue"), 10);
scores.insert(String::from("Blue"), 25);
// Only insert if key has no value
scores.entry(String::from("Yellow")).or_insert(50);
scores.entry(String::from("Blue")).or_insert(50);
// Update based on old value
let text = "hello world wonderful world";
let mut map = HashMap::new();
for word in text.split_whitespace() {
let count = map.entry(word).or_insert(0);
*count += 1;
}
println!("{:?}", map);
}Practical Examples
Example 1: Student Grade System
use std::collections::HashMap;
#[derive(Debug)]
struct Student {
name: String,
grades: Vec<f64>,
}
impl Student {
fn new(name: String) -> Self {
Student {
name,
grades: Vec::new(),
}
}
fn add_grade(&mut self, grade: f64) {
self.grades.push(grade);
}
fn average(&self) -> f64 {
if self.grades.is_empty() {
0.0
} else {
self.grades.iter().sum::<f64>() / self.grades.len() as f64
}
}
}
fn main() {
let mut students: HashMap<String, Student> = HashMap::new();
// Add students
students.insert("Alice".to_string(), Student::new("Alice".to_string()));
students.insert("Bob".to_string(), Student::new("Bob".to_string()));
// Add grades
if let Some(alice) = students.get_mut("Alice") {
alice.add_grade(95.0);
alice.add_grade(87.0);
alice.add_grade(92.0);
}
if let Some(bob) = students.get_mut("Bob") {
bob.add_grade(78.0);
bob.add_grade(85.0);
bob.add_grade(81.0);
}
// Print averages
for (name, student) in &students {
println!("{}: {:.2}", name, student.average());
}
}Example 2: Word Frequency Counter
use std::collections::HashMap;
use std::io;
fn count_words(text: &str) -> HashMap<String, usize> {
let mut word_count = HashMap::new();
for word in text.split_whitespace() {
let word = word.to_lowercase().trim_matches(|c: char| !c.is_alphabetic()).to_string();
if !word.is_empty() {
*word_count.entry(word).or_insert(0) += 1;
}
}
word_count
}
fn main() {
println!("Enter some text:");
let mut input = String::new();
io::stdin().read_line(&mut input).expect("Failed to read line");
let word_counts = count_words(&input);
println!("\nWord frequencies:");
let mut sorted_words: Vec<_> = word_counts.iter().collect();
sorted_words.sort_by(|a, b| b.1.cmp(a.1));
for (word, count) in sorted_words {
println!("{}: {}", word, count);
}
}Other Useful Collections
VecDeque (Double-ended queue)
use std::collections::VecDeque;
fn main() {
let mut deque = VecDeque::new();
deque.push_back(1);
deque.push_back(2);
deque.push_front(0);
println!("{:?}", deque); // [0, 1, 2]
if let Some(front) = deque.pop_front() {
println!("Removed from front: {}", front);
}
if let Some(back) = deque.pop_back() {
println!("Removed from back: {}", back);
}
}HashSet
use std::collections::HashSet;
fn main() {
let mut books = HashSet::new();
books.insert("A Game of Thrones");
books.insert("The Fellowship of the Ring");
books.insert("The Hitchhiker's Guide to the Galaxy");
if !books.contains("The Winds of Winter") {
println!("We don't have The Winds of Winter yet.");
}
books.remove("The Hitchhiker's Guide to the Galaxy");
for book in &books {
println!("{}", book);
}
}Performance Considerations
Vector vs VecDeque vs LinkedList
use std::collections::{VecDeque, LinkedList};
fn main() {
// Vector: Best for random access, push/pop at end
let mut vec = Vec::new();
vec.push(1);
vec.push(2);
// VecDeque: Best for push/pop at both ends
let mut deque = VecDeque::new();
deque.push_front(1);
deque.push_back(2);
// LinkedList: Best for frequent insertion/removal in middle
let mut list = LinkedList::new();
list.push_back(1);
list.push_back(2);
}Key Takeaways
- β Vectors store elements of same type in contiguous memory
- β Strings are UTF-8 encoded and growable
- β Hash Maps provide key-value storage with O(1) average access
- β Collections own their data and clean up automatically
- β Choose the right collection for your use case
- β Use iterators for efficient processing
Ready for Chapter 9? β Error Handling