CS3984 Computer Systems in Rust



Hash Tables

A HashMap in in Rust is a data structure that allows an amortized O(1) search, insert, and delete of key-value pairs.

The type of std::collections::HashMap with key type K and value type V is

pub struct HashMap<K, V>


A HashMap can be created with the new method, and the type can either be explicitly given or inferred.

use std::collections::HashMap;

fn main() {
    let map_a: HashMap<&str, i32> = HashMap::new();
    let map_b = HashMap::<&str, i32>::new();
    // Inferred to be the same type as map_a and map_b
    let mut map_c = HashMap::new();
    map_c.insert("hello", 5);
}


Inserting Values

The insert method allows inserting key-value pairs into a HashMap. The key must implement the Eq and Hash traits.

Inserting into a HashMap moves ownership, copies, or borrows depending on the type passed to HashMap.

use std::collections::HashMap;

fn main() {
    let (team, score) = (String::from("Crabs"), 5);
    let mut scores: HashMap<String, i32> = HashMap::new();
    scores.insert(team, score);
    println!("{score}"); // Cannot use `team` here

    let team = String::from("Crabs");
    let mut useless: HashMap<&str, &str> = HashMap::new();
    useless.insert(&team, &team);
    println!("{team}");
}


Retrieving Values

A reference to the value corresponding to a key can be retrieved with the get and get_mut methods:

use std::collections::HashMap;

fn main() {
    let (team, score) = (String::from("Crabs"), 5);
    let mut scores: HashMap<String, i32> = HashMap::new();
    scores.insert(team, score);

    let score_for_unknown: Option<&i32> = scores.get("Unknown");
    assert!(score_for_unknown.is_none());

    let score_for_crabs: &mut i32 = scores.get_mut("Crabs").unwrap();
    *score_for_crabs += 1;
    println!("{}", score_for_crabs);
}


Iterating Over Keys/Values/Key-Value Pairs

  • The iter/iter_mut methods return an iterator of key-value references
  • The values/values_mut methods return an iterator of value references
  • The keys method return an iterator of key references
use std::collections::HashMap;

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

  scores.insert(String::from("Blue"), 10);
  scores.insert(String::from("Yellow"), 50);

  for (key, value) in scores.iter() {
      println!("{key}: {value}");
  }
}


Updating Values

  1. Overwriting a Value

You can overwrite key-value pairs by calling insert:

use std::collections::HashMap;

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

  scores.insert(String::from("Blue"), 10);
  scores.insert(String::from("Blue"), 50);
  // {"Blue": 50}
  println!("{scores:?}");
}


Updating Values (2)

  1. Inserting a value if the key does not exist

You can use the Entry API:

use std::collections::HashMap;

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

  scores.insert(String::from("Blue"), 10);

  scores.entry(String::from("Blue")).or_insert(50);
  scores.entry(String::from("Green")).or_insert(50);

  // {"Blue": 10, "Green": 50}
  println!("{scores:?}");
}


Updating Values (3)

  1. Updating a value with a default if the key does not exist

The or_insert method returns a mutable reference that can be used:

use std::collections::HashMap;

fn main() {
  let words = ["a", "b", "a", "a", "c", "d", "b"];
  let mut counts = HashMap::new();

  for word in words {
      let count = counts.entry(word).or_insert(0);
      *count += 1;
  }

  // {"b": 2, "c": 1, "d": 1, "a": 3}
  println!("{counts:?}");
}