HashMap
In Rust programs we use a HashMap
to link keys with values. We could loop over vectors to keep track of this relation, but a HashMap
is often faster.
Collection
detailsPart of std, we gain access to HashMap
in Rust with a "use" directive. We can insert get values, and loop over the entire collection.
HashMap
To begin, we create a HashMap
and specify the key and value types. The type of "animals" could be omitted, and the compiler would resolve it for us.
HashMap::new()
we create a HashMap
with String
keys and integer values. We add two keys with one value each to the map.HashMap
at the key "bird" which resolves to the value 100 (this was added in the first insert call).use std::collections::HashMap; fn main() { // Step 1: create HashMap and insert data. let mut animals: HashMap<String, u32> = HashMap::new(); animals.insert("bird".to_string(), 100); animals.insert("cat".to_string(), 200); // Step 2: get value from HashMap. println!("{}", animals.get("bird").unwrap()); }100
For
-loopCritical to using a HashMap
in many programs is looping over the collection. For the clearest code, we call iter()
and loop over the key and values in one loop.
HashMap
and then add 2 keys and 2 values. The compiler figures out the HashMap
has string
keys and values.for-in
loop syntax, and iterate over HashMap
by calling iter()
. We print all the keys, and each key's associated value.use std::collections::HashMap; fn main() { // Part 1: create new HashMap. let mut items = HashMap::new(); items.insert("tree", "green"); items.insert("hat", "yellow"); // Part 2: call iter to loop over the keys and values. for (key, value) in items.iter() { println!("ITER KEY, VALUE: {} {}", key, value); } }ITER KEY, VALUE: hat yellow ITER KEY, VALUE: tree green
Match
get exampleSometimes a key is not found in a HashMap
. In this case, get()
will return None
—we can use a match expression to handle a key that is not found.
HashMap
contains the keys "green" and "red," but we try to access "orange." The None
case in match is selected.use std::collections::HashMap; fn main() { let mut colors = HashMap::new(); colors.insert("green", 1); colors.insert("red", 5); // Use match to handle keys that are not found. match colors.get("orange") { Some(value) => println!("VALUE: {}", value), None => println!("NOT FOUND") } }NOT FOUND
Contains_key
Sometimes we want to see if a key exists in the HashMap
, and we don't need the value. We can use contains_key
for this purpose—it returns a boolean.
use std::collections::HashMap; fn main() { let mut zoo = HashMap::new(); zoo.insert("bird", 10); // See if bird is found in the zoo HashMap. if zoo.contains_key("bird") { println!("CONTAINS BIRD") } }CONTAINS BIRD
We can access the count of keys in a HashMap
with the len
and is_empty
functions. When zero entries are present, is_empty()
returns true.
len()
returns the number of keys in the HashMap
.use std::collections::HashMap; fn main() { let mut ids: HashMap<u32, u32> = HashMap::new(); // A HashMap starts out empty. if ids.is_empty() { println!("EMPTY"); } // Add 2 entries. ids.insert(1, 10); ids.insert(2, 20); // Print count of keys. println!("LEN: {}", ids.len()); }EMPTY LEN: 2
How can we handle the Option
from calling get()
on a HashMap
in an elegant way? With unwrap_or
, we can specify a default value in case of a None
result.
HashMaps
with a String
value, we may need to create a local variable to pass to unwrap_or
.String::new()
does not allocate as it is an empty string
. So this approach to getting values should be fast.use std::collections::HashMap; fn main() { let mut map: HashMap<String, String> = HashMap::new(); map.insert("bird".to_string(), "blue".to_string()); // Use unwrap_or to simplify getting values from HashMap. let default = String::new(); let result = map.get("bird").unwrap_or(&default); println!("RESULT: {}", result); // Not found returns the argument to unwrap_or. let result = map.get("not found").unwrap_or(&default); println!("RESULT: {}", result); }RESULT: blue RESULT:
In Rust, arrays can be created on the stack as they are a fixed size. This means we can create arrays to look up values in a HashMap
quickly.
HashMap
keys. We can then create arrays (on the stack) and look up values from the HashMap
.HashMap
with small array keys can be fast, as no heap allocations are required.HashMap
keys, but vectors have a variable size so they must be stored on the (slower) heap.use std::collections::*; fn main() { // Create HashMap and insert 1 array key. let mut map = HashMap::new(); map.insert([b'x', 0, 0], "cat"); // Create an array and use it to access a value from the HashMap. let mut key = [0; 3]; key[0] = b'x'; if let Some(result) = map.get(&key) { println!("{result}"); } }cat
When we insert()
a key into a HashMap
, we mutate the HashMap
. If the HashMap
was not declared as mutable (with "mut
") this will cause an error.
HashMap
variables as mut
. If we do not we will get a compile-time error.| 7 | let items = HashMap::new(); | ----- help: consider changing this to be mutable: mut items 8 | items.insert("tree", "green"); | ^^^^^ cannot borrow as mutable error[E0596]: cannot borrow items as mutable, as it is not declared as mutable --> src\main.rs:9:5
HashMap
, BTreeMap
benchmarkWe can use a BTreeMap
instead of a HashMap
in Rust programs. Often just the type name must be changed. BTreeMap
has different performance characteristics.
HashMap
repeatedly.HashMap
code, but with a BTreeMap
instead—the logic is the same.HashMap
performed significantly faster. This is usually (but not always) the case when comparing HashMap
and BTreeMap
.use std::collections::*; use std::time::*; fn main() { let mut map = HashMap::new(); map.insert("bird", 0); map.insert("frog", 0); map.insert("dog", 0); let mut bt = BTreeMap::new(); bt.insert("bird", 0); bt.insert("frog", 0); bt.insert("dog", 0); if let Ok(max) = "100000000".parse::<usize>() { let mut count = 0; // Version 1: use HashMap. let t0 = Instant::now(); for _ in 0..max { if let Some(result) = map.get("frog") { count += 1; } } println!("{} ms", t0.elapsed().as_millis()); // Version 2: use BTreeMap. let t1 = Instant::now(); for _ in 0..max { if let Some(result) = bt.get("frog") { count += 1; } } println!("{} ms", t1.elapsed().as_millis()); println!("{}", count); } } 884 ms HashMap get 1249 ms BTreeMap get 200000000
Sort
HashMap
We cannot directly sort a HashMap
, but we can sort the keys and values themselves. And then we can loop over those sorted values.
HashMap
Suppose we have a HashMap
, and we want to have a Vector
—we can perform a conversion to get a vector. And the opposite can be done as well.
HashMap
supports efficient, hashed lookup from key to value. Rust provides complete support for this kind of collection—we specify key and value types.