Collections: Vec, String, and HashMap

Module 1's arrays and tuples were fixed-size and stack-bound. Real programs need growable, heap-backed collections — and Rust's three workhorses, Vec, String, and HashMap, are where ownership and borrowing stop being abstract rules and become the everyday texture of writing Rust. This opens Module 2: the data structures and abstractions you actually build with.

Module 1 built Rust’s foundations — ownership, borrowing, lifetimes, enums, error handling. Module 2 covers what you build with them, starting with the collections you’ll use constantly: Vec<T> (a growable list), String (growable, owned text), and HashMap<K, V> (key-value lookup). These are heap-backed and dynamically sized, unlike Module 1’s fixed arrays and tuples, and working with them is where ownership becomes concrete daily practice. This post covers all three and the ownership patterns they teach.

Vec: the growable list

Vec<T> is Rust’s growable, heap-allocated list of values of a single type T — the collection you reach for most:

let mut nums: Vec<i32> = Vec::new();
nums.push(1);
nums.push(2);
nums.push(3);

let letters = vec!['a', 'b', 'c'];   // the vec! macro for literals

for n in &nums {                     // iterate by reference (borrow)
    println!("{}", n);
}
println!("first: {}", nums[0]);      // index access

A Vec owns its elements (they live on the heap), grows as you push, and is dropped — freeing its elements — when it goes out of scope (ownership rules from Module 1, applied to a collection). Two Rust-specific things to note:

String: growable, owned text

Module 1 mentioned &str (a string slice) in passing. String is the growable, heap-allocated, owned counterpart — and the String vs &str distinction is one Rust newcomers must get straight:

let mut s = String::new();
s.push_str("hello");
s.push(' ');
s.push_str("world");             // s is now "hello world"

let literal: &str = "baked in";  // a &str borrowing static data
let owned: String = literal.to_string();   // convert &str -> owned String
let view: &str = &s;             // borrow a String as a &str

The pattern to internalize: take &str as function parameters, return/store String when you need ownership. A function that just reads text should accept &str (it can then take both a String (via a reference) and a literal — maximum flexibility, no ownership taken); a function that produces or stores text returns/keeps a String. This &str-for-borrowing, String-for-owning split is the string-level expression of the ownership/borrowing model, and getting it right makes string code both efficient and ergonomic. (Also note: Rust strings are UTF-8, so you don’t index by byte position casually — you iterate .chars() or .bytes() — because a char may be multiple bytes, the Unicode point from Module 1.)

HashMap: key-value lookup

HashMap<K, V> stores key-value pairs with fast lookup by key — Rust’s dictionary/map:

use std::collections::HashMap;

let mut scores: HashMap<String, i32> = HashMap::new();
scores.insert(String::from("alice"), 10);
scores.insert(String::from("bob"), 7);

// safe lookup returns Option<&V>:
match scores.get("alice") {
    Some(score) => println!("alice: {}", score),
    None => println!("no score"),
}

// the entry API: insert-or-update in one idiomatic move
*scores.entry(String::from("alice")).or_insert(0) += 5;   // alice -> 15

Two HashMap idioms worth learning:

Note that HashMap needs its keys to be hashable and comparable (they implement the relevant traits — the subject of the traits post), and inserting a String key moves it into the map (ownership, again). The collection follows the same ownership rules as everything else.

Collections make ownership concrete

The deeper point of this post: collections are where Module 1’s rules become daily practice, because collections own their contents and you constantly borrow into them:

So collections aren’t just data structures — they’re where you feel ownership and borrowing working, every day. If Module 1 taught the rules in the abstract, Vec/String/HashMap are where they click into muscle memory. (Rust’s standard library has more collections — VecDeque, BTreeMap, HashSet, and others — but these three cover the vast majority of needs and teach the patterns the rest follow.) The next post covers writing code that works over many types — generics.

Key takeaways

Further reading

Sources & References