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

πŸ¦€ Rust Programming Tutorial

Learn from Zero to Advanced

Built with Next.js and Tailwind CSS β€’ Open Source