Collections

Overview

Collections are data structures that can hold multiple values and grow or shrink at runtime. Unlike arrays and tuples (stored on the stack with fixed size), collections store data on the heap.

flowchart TD
    subgraph "Choose Your Collection"
        A{Need order?}
        A -->|Yes, by index| B["Vec or VecDeque"]
        A -->|Yes, sorted| C["BTreeMap or BTreeSet"]
        A -->|No| D{Need key-value?}
        D -->|Yes| E["HashMap"]
        D -->|No, unique values| F["HashSet"]
    end

Key insight: Choose collections based on your access patterns—Vec for indexed access, HashMap for key lookup, HashSet for membership testing.

When to Use Each Collection

Collection Best For Avoid When
Vec<T> Ordered sequences, stack-like access Need fast key lookup
HashMap<K,V> Fast key-based lookup Need ordering
HashSet<T> Unique values, membership tests Need duplicates
VecDeque<T> Push/pop both ends (queue) Only need one end
BTreeMap<K,V> Sorted key order, range queries Unordered is fine
BinaryHeap<T> Priority queue, always get max Need arbitrary access
flowchart TD
    subgraph "Performance Comparison"
        direction LR
        V["Vec"]
        H["HashMap"]
        B["BTreeMap"]
    end

    subgraph "Operations"
        V --> V1["Index: O(1)"]
        V --> V2["Push/Pop: O(1)"]
        V --> V3["Search: O(n)"]

        H --> H1["Get: O(1) avg"]
        H --> H2["Insert: O(1) avg"]
        H --> H3["Not ordered"]

        B --> B1["Get: O(log n)"]
        B --> B2["Insert: O(log n)"]
        B --> B3["Sorted iteration"]
    end

Vec - Dynamic Arrays

Creating Vectors

flowchart LR
    subgraph "Stack"
        V["Vec metadata"]
        V1["ptr"]
        V2["len: 3"]
        V3["capacity: 4"]
    end

    subgraph "Heap"
        H["[1, 2, 3, _]"]
    end

    V1 --> H
fn main() {
    // Empty vector with type annotation
    let v: Vec<i32> = Vec::new();

    // Using vec! macro
    let v = vec![1, 2, 3];

    // With capacity
    let mut v = Vec::with_capacity(10);
}

Adding Elements

fn main() {
    let mut v = Vec::new();

    v.push(1);
    v.push(2);
    v.push(3);

    // Extend from iterator
    v.extend([4, 5, 6]);
}

Accessing Elements

fn main() {
    let v = vec![1, 2, 3, 4, 5];

    // Index (panics if out of bounds)
    let third = v[2];

    // get() returns Option (safe)
    match v.get(2) {
        Some(x) => println!("Third element: {}", x),
        None => println!("No third element"),
    }

    // First and last
    let first = v.first();  // Option<&T>
    let last = v.last();    // Option<&T>
}

Iterating

fn main() {
    let v = vec![1, 2, 3];

    // Immutable iteration
    for i in &v {
        println!("{}", i);
    }

    // Mutable iteration
    let mut v = vec![1, 2, 3];
    for i in &mut v {
        *i += 10;
    }

    // Consuming iteration
    for i in v {
        println!("{}", i);
    }
    // v is no longer valid
}

Common Operations

fn main() {
    let mut v = vec![1, 2, 3, 4, 5];

    // Length and capacity
    println!("len: {}, capacity: {}", v.len(), v.capacity());

    // Remove elements
    let last = v.pop();              // Remove last
    let second = v.remove(1);        // Remove at index
    v.retain(|x| *x > 2);           // Keep elements matching predicate

    // Insert
    v.insert(0, 10);                 // Insert at index

    // Sort
    v.sort();
    v.sort_by(|a, b| b.cmp(a));     // Reverse sort

    // Dedup
    v.dedup();                       // Remove consecutive duplicates
}

HashMap<K, V>

A hash map stores key-value pairs with O(1) average lookup time.

flowchart LR
    subgraph "HashMap"
        K1["'Blue'"] --> V1["10"]
        K2["'Red'"] --> V2["50"]
        K3["'Green'"] --> V3["25"]
    end

    Q["scores.get('Blue')"] --> V1

Creating HashMaps

use std::collections::HashMap;

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

    // From iterators
    let teams = vec![String::from("Blue"), String::from("Red")];
    let initial_scores = vec![10, 50];
    let scores: HashMap<_, _> = teams.into_iter().zip(initial_scores).collect();
}

Inserting and Updating

use std::collections::HashMap;

fn main() {
    let mut scores = HashMap::new();

    // Insert
    scores.insert(String::from("Blue"), 10);
    scores.insert(String::from("Red"), 50);

    // Overwrite
    scores.insert(String::from("Blue"), 25);

    // Insert only if key doesn't exist
    scores.entry(String::from("Yellow")).or_insert(50);

    // Update based on old value
    let count = scores.entry(String::from("Blue")).or_insert(0);
    *count += 1;
}

Accessing Values

use std::collections::HashMap;

fn main() {
    let mut scores = HashMap::new();
    scores.insert(String::from("Blue"), 10);

    // get() returns Option<&V>
    let score = scores.get("Blue");

    // Direct access (panics if missing in some APIs)
    if let Some(s) = scores.get("Blue") {
        println!("Score: {}", s);
    }

    // Check existence
    if scores.contains_key("Blue") {
        println!("Blue team exists");
    }
}

Iterating

use std::collections::HashMap;

fn main() {
    let scores = HashMap::from([
        (String::from("Blue"), 10),
        (String::from("Red"), 50),
    ]);

    // Iterate over key-value pairs
    for (key, value) in &scores {
        println!("{}: {}", key, value);
    }

    // Keys only
    for key in scores.keys() {
        println!("{}", key);
    }

    // Values only
    for value in scores.values() {
        println!("{}", value);
    }
}

String

Strings in Rust are UTF-8 encoded.

String Types

Type Description
String Owned, growable
&str Borrowed string slice
&'static str String literal

Creating Strings

fn main() {
    // From literal
    let s = String::from("hello");
    let s = "hello".to_string();

    // Empty
    let s = String::new();

    // With capacity
    let s = String::with_capacity(10);
}

Modifying Strings

fn main() {
    let mut s = String::from("hello");

    // Append
    s.push(' ');           // Single char
    s.push_str("world");   // String slice

    // Concatenate
    let s1 = String::from("Hello, ");
    let s2 = String::from("world!");
    let s3 = s1 + &s2;     // s1 is moved

    // format! macro (doesn't move)
    let s1 = String::from("Hello");
    let s2 = String::from("world");
    let s3 = format!("{}, {}!", s1, s2);
}

Indexing and Slicing

fn main() {
    let s = String::from("hello");

    // Cannot index directly (UTF-8 safety)
    // let c = s[0];  // Error!

    // Slice by byte range (must be valid UTF-8 boundary)
    let hello = &s[0..5];

    // Iterate over chars
    for c in s.chars() {
        println!("{}", c);
    }

    // Iterate over bytes
    for b in s.bytes() {
        println!("{}", b);
    }
}

String indexing by byte position can panic if you split a multi-byte character.

Common Operations

fn main() {
    let s = String::from("  hello world  ");

    // Trim whitespace
    let trimmed = s.trim();

    // Split
    for word in s.split_whitespace() {
        println!("{}", word);
    }

    // Replace
    let new_s = s.replace("hello", "hi");

    // Contains
    if s.contains("hello") {
        println!("Found!");
    }

    // Length
    println!("Bytes: {}", s.len());
    println!("Chars: {}", s.chars().count());
}

Other Collections

Collection Use Case
VecDeque<T> Double-ended queue
LinkedList<T> Linked list (rarely needed)
HashSet<T> Unique values, fast lookup
BTreeMap<K, V> Sorted map
BTreeSet<T> Sorted set
BinaryHeap<T> Priority queue

HashSet Example

use std::collections::HashSet;

fn main() {
    let mut books = HashSet::new();

    books.insert("A Game of Thrones");
    books.insert("The Name of the Wind");
    books.insert("A Game of Thrones");  // Duplicate ignored

    println!("Books: {}", books.len());  // 2

    // Set operations
    let set1: HashSet<_> = [1, 2, 3].into_iter().collect();
    let set2: HashSet<_> = [2, 3, 4].into_iter().collect();

    let intersection: HashSet<_> = set1.intersection(&set2).collect();
    let union: HashSet<_> = set1.union(&set2).collect();
}

Summary

mindmap
  root((Collections))
    Sequence
      Vec dynamic array
      VecDeque double-ended
      LinkedList rarely needed
    Map
      HashMap fast lookup
      BTreeMap sorted keys
    Set
      HashSet unique values
      BTreeSet sorted unique
    Priority
      BinaryHeap max heap
Collection Ordered Unique Access Use Case
Vec<T> Yes No Index O(1) Default choice for lists
VecDeque<T> Yes No Index O(1) Queue/deque operations
HashMap<K,V> No Keys Key O(1)* Fast key-value lookup
HashSet<T> No Yes Contains O(1)* Membership testing
BTreeMap<K,V> Sorted Keys Key O(log n) Range queries, sorted iteration
BTreeSet<T> Sorted Yes Contains O(log n) Sorted unique values
BinaryHeap<T> Heap No Max O(1) Priority queue

*O(1) average, O(n) worst case

Exercises

  1. Count word frequency in a text using HashMap
  2. Remove duplicates from a Vec while preserving order
  3. Implement a simple LRU cache using HashMap and VecDeque

See Also

Next Steps

Learn about Iterators to process collections efficiently.


Back to top

Rust Programming Guide is not affiliated with the Rust Foundation. Content is provided for educational purposes.

This site uses Just the Docs, a documentation theme for Jekyll.