HashMap in Rust: Ownership, Iterators & Map Types

Max WellsMax WellsFounder of Rustify

TL;DR: HashMap<K, V> is Rust's standard hash map, a collection that stores key-value pairs and provides O(1) average-time lookup, insertion, and deletion. Keys must implement Hash and Eq. Like all Rust collections, HashMap owns its keys and values, inserting a value moves ownership into the map. Rust's HashMap uses the SipHash algorithm by default, which is DoS-resistant but slightly slower than non-cryptographic alternatives like FxHashMap or AHashMap from popular crates.


What Is HashMap<K, V> in Rust?

HashMap<K, V> maps keys of type K to values of type V with O(1) average-time access ; Rust's equivalent of Python's dict or JavaScript's Map.

use std::collections::HashMap;
 
fn main() {
    let mut scores: HashMap<String, u32> = HashMap::new();
 
    // Insert
    scores.insert("Alice".to_string(), 100);
    scores.insert("Bob".to_string(), 85);
    scores.insert("Carol".to_string(), 92);
 
    // Lookup; returns Option<&V>
    if let Some(score) = scores.get("Alice") {
        println!("Alice's score: {score}");
    }
 
    // Direct access; panics if key missing
    println!("{}", scores["Bob"]);
 
    // Check existence
    println!("{}", scores.contains_key("Dave")); // false
 
    // Length
    println!("{}", scores.len()); // 3
}

How Does Ownership Work With HashMap?

HashMap takes ownership of inserted keys and values. For Copy types (integers), values are copied in. For heap types (String, Vec), values are moved in.

use std::collections::HashMap;
 
fn main() {
    let key = String::from("color");
    let value = String::from("blue");
 
    let mut map = HashMap::new();
    map.insert(key, value);
 
    // println!("{key}"); // COMPILE ERROR: key was moved into the map
    // println!("{value}"); // COMPILE ERROR: value was moved
 
    // Use references to avoid moving:
    map.insert("size".to_string(), "large".to_string());
 
    // .get() returns &V; borrows from the map, doesn't move
    let color: Option<&String> = map.get("color");
}

What Are the Key HashMap Operations?

entry() is the most powerful API, it lets you insert-or-update without a double lookup.

use std::collections::HashMap;
 
fn main() {
    let text = "hello world hello rust hello";
    let mut word_count: HashMap<&str, u32> = HashMap::new();
 
    for word in text.split_whitespace() {
        // entry().or_insert(); insert default if absent, return &mut V
        let count = word_count.entry(word).or_insert(0);
        *count += 1;
    }
    println!("{:?}", word_count);
    // {"hello": 3, "world": 1, "rust": 1}
 
    // or_insert_with; lazily compute default (avoids allocation if present)
    word_count.entry("rust").or_insert_with(|| 0);
 
    // and_modify; update only if present
    word_count.entry("hello").and_modify(|c| *c *= 2);
 
    // Remove
    let removed = word_count.remove("world"); // Some(1)
 
    // Iterate
    for (word, count) in &word_count {
        println!("{word}: {count}");
    }
}

How Do You Build a HashMap From an Iterator?

Collect an iterator of (key, value) tuples directly into a HashMap using .collect().

use std::collections::HashMap;
 
fn main() {
    // From two vecs zipped together
    let keys = vec!["a", "b", "c"];
    let vals = vec![1, 2, 3];
    let map: HashMap<&str, i32> = keys.into_iter().zip(vals).collect();
 
    // From transforming a vec
    let words = vec!["hello", "world", "rust"];
    let lengths: HashMap<&str, usize> = words
        .iter()
        .map(|&w| (w, w.len()))
        .collect();
 
    println!("{:?}", lengths); // {"hello": 5, "world": 5, "rust": 4}
}

What Other Map Types Are Available?

TypeUse case
HashMap<K, V>Default ; SipHash, DoS-resistant
BTreeMap<K, V>Keys always sorted; O(log n) access
IndexMap<K, V> (indexmap crate)Preserves insertion order
FxHashMap (rustc-hash crate)Faster hash, not DoS-resistant, use in trusted environments
AHashMap (ahash crate)Fast alternative used by many frameworks
DashMap (dashmap crate)Concurrent hash map, safe across threads

BTreeMap is the right choice when you need sorted iteration over keys, or when implementing a range-query structure.


Frequently Asked Questions

HashMap uses hashing, the bucket a key lands in depends on its hash, not insertion time. If you need ordered iteration, use BTreeMap (sorted by key) or IndexMap (preserves insertion order).

Any type implementing Hash + Eq. This includes all primitive types, String, &str, tuples of hashable types, and arrays. Floats (f32, f64) do NOT implement Hash (because NaN != NaN): you cannot use them as keys. Use an ordered integer representation instead.

.get("key").unwrap_or(&default) or .get("key").copied().unwrap_or(0) for Copy types. For the insert-if-absent pattern, use .entry("key").or_insert(default).

No, HashMap is not Sync. For concurrent access, use DashMap (from the dashmap crate), or wrap in Arc<Mutex<HashMap<K, V>>>. For read-heavy workloads, Arc<RwLock<HashMap<K, V>>> allows concurrent reads.


Sources


  • Ownership: Inserted keys and values are moved into the map
  • Generic: HashMap<K, V> is generic over key and value types
  • Iterator: HashMaps can be built from and iterated with iterators
  • Vec: Vec and HashMap are the two most-used collections

Keep Reading

Ready to Land a $120k+ Rust Job in the US or Europe?