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 details. Part of std, we gain access to HashMap in Rust with a "use" directive. We can insert get values, and loop over the entire collection.
New 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.
Step 1 We create an animals HashMap with String keys and integer values. We add 2 keys with 1 associated value each to the map.
Step 2 We print the value of the 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-loop. Critical 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.
Part 1 We create a new HashMap and then add 2 keys and 2 values. The compiler figures out the HashMap has string keys and values.
Part 2 We use the 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 example. Sometimes 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.
Here The 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
Len. 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.
Tip Each entry (a key-value pair) counts as 1—so 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
Unwrap or. 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.
Tip For HashMaps with a String value, we may need to create a local variable to pass to unwrap_or.
Info Calling 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:
Array keys. 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.
Start We create 3-element arrays for the HashMap keys. We can then create arrays (on the stack) and look up values from the HashMap.
Tip In benchmarks, using a HashMap with small array keys can be fast, as no heap allocations are required.
Also We can use vectors as 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
Mut error. 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.
So We should usually declare 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 benchmark. We can use a BTreeMap instead of a HashMap in Rust programs. Often just the type name must be changed. BTreeMap has different performance characteristics.
Version 1 This version of the code tries to access the "frog" key of the HashMap repeatedly.
Version 2 Here we do the same thing as the HashMap code, but with a BTreeMap instead—the logic is the same.
Result The 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.
Convert 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.
Summary. HashMap supports efficient, hashed lookup from key to value. Rust provides complete support for this kind of collection—we specify key and value types.
Dot Net Perls is a collection of tested code examples. Pages are continually updated to stay current, with code correctness a top priority.
Sam Allen is passionate about computer languages. In the past, his work has been recommended by Apple and Microsoft and he has studied computers at a selective university in the United States.
This page was last updated on Nov 20, 2024 (edit link).