Learn › Rust Programming

Collections

Work with Rust's essential collections: Vec, HashMap, String, and iterators.

Vectors

A Vec is a growable, heap-allocated array that can store elements of the same type. Vectors are the most commonly used collection in Rust and provide methods for adding, removing, and accessing elements. You can create a vector with Vec::new or the vec! macro. Accessing elements by index can panic if the index is out of bounds, but the get method returns an Option that lets you handle the missing case safely.

fn main() {
    // Creating vectors
    let mut numbers: Vec<i32> = Vec::new();
    numbers.push(10);
    numbers.push(20);
    numbers.push(30);

    let colors = vec!["red", "green", "blue"];

    // Accessing elements
    println!("First number: {}", numbers[0]);
    println!("Safe access: {:?}", numbers.get(99));

    // Iterating
    for num in &numbers {
        print!("{} ", num);
    }
    println!();

    // Useful methods
    numbers.sort();
    numbers.reverse();
    println!("Contains 20: {}", numbers.contains(&20));
    println!("Length: {}", numbers.len());

    // Functional operations
    let doubled: Vec<i32> = numbers.iter().map(|x| x * 2).collect();
    println!("Doubled: {:?}", doubled);

    let sum: i32 = numbers.iter().sum();
    println!("Sum: {}", sum);

    // Retain only even numbers
    let mut vals = vec![1, 2, 3, 4, 5, 6, 7, 8];
    vals.retain(|x| x % 2 == 0);
    println!("Evens: {:?}", vals);
}

HashMap

HashMap stores key-value pairs and provides average O(1) lookup time. Keys must implement the Eq and Hash traits, which most standard types already do. You can insert entries, look up values by key, update existing entries, and iterate over all key-value pairs. The entry API provides an elegant way to insert a value only if the key does not already exist, which is useful for counting and grouping operations.

use std::collections::HashMap;

fn main() {
    let mut scores: HashMap<String, i32> = HashMap::new();

    // Inserting
    scores.insert("Alice".to_string(), 95);
    scores.insert("Bob".to_string(), 87);
    scores.insert("Charlie".to_string(), 92);

    // Accessing
    if let Some(score) = scores.get("Alice") {
        println!("Alice's score: {}", score);
    }

    // Entry API - insert if absent
    scores.entry("Diana".to_string()).or_insert(88);
    scores.entry("Alice".to_string()).or_insert(0); // won't overwrite

    // Iterating
    for (name, score) in &scores {
        println!("{}: {}", name, score);
    }

    // Word frequency counter
    let text = "the cat sat on the mat the cat";
    let mut word_count: HashMap<&str, i32> = HashMap::new();

    for word in text.split_whitespace() {
        let count = word_count.entry(word).or_insert(0);
        *count += 1;
    }

    println!("
Word frequencies:");
    for (word, count) in &word_count {
        println!("  '{}': {}", word, count);
    }
}

Strings

Rust has two main string types: String, which is a growable heap-allocated UTF-8 string, and &str, which is a string slice that references a portion of string data. String owns its data while &str borrows it. Concatenation can be done with the push_str method, the + operator, or the format! macro. Because Rust strings are UTF-8 encoded, indexing by byte position is not directly supported since a single character can span multiple bytes.

fn main() {
    // Creating strings
    let mut greeting = String::from("Hello");
    greeting.push_str(", World");
    greeting.push('!');
    println!("{}", greeting);

    // String formatting
    let name = "Rust";
    let version = 2024;
    let formatted = format!("{} v{}", name, version);
    println!("{}", formatted);

    // String methods
    let sentence = "  Hello, Rust Programming!  ";
    println!("Trimmed: '{}'", sentence.trim());
    println!("Uppercase: {}", sentence.to_uppercase());
    println!("Contains 'Rust': {}", sentence.contains("Rust"));
    println!("Replace: {}", sentence.replace("Rust", "Systems"));

    // Splitting and collecting
    let csv = "apple,banana,cherry,date";
    let fruits: Vec<&str> = csv.split(',').collect();
    println!("Fruits: {:?}", fruits);

    // Iterating over characters
    let word = "hello";
    let capitalized: String = word
        .chars()
        .enumerate()
        .map(|(i, c)| {
            if i == 0 { c.to_uppercase().next().unwrap() } else { c }
        })
        .collect();
    println!("Capitalized: {}", capitalized);
}

Iterators

Iterators are a central abstraction in Rust that provide a lazy, composable way to process sequences of values. The Iterator trait requires implementing the next method which returns Option to signal when the sequence is exhausted. Iterator adaptors like map, filter, and take transform iterators without consuming them. Collecting or calling a consuming adaptor like sum or count triggers the actual computation. Iterators in Rust are zero-cost abstractions that compile down to code as efficient as hand-written loops.

fn main() {
    let numbers = vec![1, 2, 3, 4, 5, 6, 7, 8, 9, 10];

    // Chaining iterator operations
    let result: Vec<i32> = numbers
        .iter()
        .filter(|&&x| x % 2 == 0)
        .map(|&x| x * x)
        .collect();
    println!("Even squares: {:?}", result);

    // fold (reduce)
    let product: i32 = numbers.iter().fold(1, |acc, &x| acc * x);
    println!("Product of 1..10: {}", product);

    // zip combines two iterators
    let names = vec!["Alice", "Bob", "Charlie"];
    let ages = vec![30, 25, 35];
    let people: Vec<_> = names.iter().zip(ages.iter()).collect();
    println!("People: {:?}", people);

    // enumerate gives index and value
    for (i, name) in names.iter().enumerate() {
        println!("  {}: {}", i, name);
    }

    // Chaining and flattening
    let nested = vec![vec![1, 2], vec![3, 4], vec![5, 6]];
    let flat: Vec<&i32> = nested.iter().flat_map(|v| v.iter()).collect();
    println!("Flattened: {:?}", flat);

    // any, all, find
    let has_negative = numbers.iter().any(|&x| x < 0);
    let all_positive = numbers.iter().all(|&x| x > 0);
    let first_gt_5 = numbers.iter().find(|&&x| x > 5);
    println!("Has negative: {}", has_negative);
    println!("All positive: {}", all_positive);
    println!("First > 5: {:?}", first_gt_5);
}

← Error Handling · Concurrency →