Rust HashMap Examples

Simple easy to understand Rust HashMap Examples

In this tutorial we want to learn about HashMap by looking at several easy to understand examples.

[lwptoc]

Example 1: Rust HashMap Example

An easy to understand Rust Hashmap example.

Step 1: Dependencies

No dependencies are needed.

Step 2: Write Code

Here is the full code:

use std::collections::HashMap;
use std::collections::hash_map::Entry::{Occupied, Vacant};

fn main() {
    println!("Using borrowed pointers as keys.");
    let mut h: HashMap<&str, isize>;
    h = HashMap::new();
    h.insert("foo", 42);
    println!("Is there a key foo?  => {}", h.contains_key(&("foo"))); // => true
    println!("Is there a key baz?  => {}", h.contains_key(&("baz"))); // => false
    println!("The value for foo is => {:?}", h.get(&("foo"))); // => Some(&42)
    let key = "baz";
    h.insert(key, 1);
    println!("Is there a key baz?  => {}", h.contains_key(&("baz"))); // => false

    // Doing a find, inserting with a proc(), using the key to construct the value
    let mut map = HashMap::<String, String>::new();
    match map.entry("foo".to_string()) {
        Vacant(entry) => { entry.insert("bar".to_string()); },
        Occupied(mut entry) => { entry.get_mut().push_str("bar"); },
    }
    println!("The value for foo is => {:?}", map.get(&("foo".to_string()))); // => Some(&~"foobar")
    // running this for the first time, will add "foo" with the value 1
    // running the same for the second time, will add +1 to "foo"
    match h.entry("foo") {
        Vacant(entry) => { entry.insert(1); },
        Occupied(mut entry) => { *entry.get_mut() += 1; },
    }
    println!("foo={:?}", h.get(&("foo")));
    assert_eq!(h["foo"], 43);

    // You don't actually need the HashMap to own the keys (but
    // unless all keys are static, this will be likely to lead
    // to problems, so I don't suggest you do it in reality)

    println!("Using owned pointers as keys.");
    let mut h = HashMap::<String, isize>::new();
    h.insert("foo".to_string(), 42);
    println!("Is there a key foo?  => {}", h.contains_key(&"foo".to_string())); // => true
    println!("Is there a key baz?  => {}", h.contains_key(&"baz".to_string())); // => false
    println!("The value for foo is => {:?}", h.get(&"foo".to_string())); // => Some(&42)
    h.insert(key.to_string(), 1);
    println!("Is there a key baz?  => {}", h.contains_key(&"baz".to_string())); // => true

    // List keys of the HashMap
    let mut keys: Vec<String> = Vec::new();
    for (k, _) in h.iter() {
        keys.push(k.to_string());
    }
    println!("These are the keys: {:?}.", keys);
    let keys = h.keys().map(|v| v.clone()).collect::<Vec<String>>();
    println!("These are the keys: {:?}.", keys);

    // List values of the HashMap
    let values = h.values().map(|v| v.clone()).collect::<Vec<isize>>();
    println!("These are the values: {:?}.", values);

    // type inference lets us omit an explicit type signature (which
    // would be HashMap<&str, &str> in this example).
    let mut book_reviews = HashMap::new();

    // review some books.
    book_reviews.insert("Adventures of Huckleberry Finn",    "My favorite book.");
    book_reviews.insert("Grimms' Fairy Tales",               "Masterpiece.");
    book_reviews.insert("Pride and Prejudice",               "Very enjoyable.");
    book_reviews.insert("The Adventures of Sherlock Holmes", "Eye lyked it alot.");

    // check for a specific one.
    if !book_reviews.contains_key(&("Les Misérables")) {
        println!("We've got {} reviews, but Les Misérables ain't one.",
             book_reviews.len());
    }

    // oops, this review has a lot of spelling mistakes, let's delete it.
    book_reviews.remove(&("The Adventures of Sherlock Holmes"));

    // look up the values associated with some keys.
    let to_find = ["Pride and Prejudice", "Alice's Adventure in Wonderland"];
    for book in to_find.iter() {
        match book_reviews.get(book) {
            Some(review) => println!("{}: {}", *book, *review),
            None => println!("{} is unreviewed.", *book)
        }
    }

    // iterate over everything.
    for (book, review) in book_reviews.iter() {
        println!("{}: \"{}\"", *book, *review);
    }
}

Step 3: Run

Run the code and you will get the following:

Using borrowed pointers as keys.
Is there a key foo?  => true
Is there a key baz?  => false
The value for foo is => Some(42)
Is there a key baz?  => true
The value for foo is => Some("bar")
foo=Some(43)
Using owned pointers as keys.
Is there a key foo?  => true
Is there a key baz?  => false
The value for foo is => Some(42)
Is there a key baz?  => true
These are the keys: ["baz", "foo"].
These are the keys: ["baz", "foo"].
These are the values: [1, 42].
We've got 4 reviews, but Les Misérables ain't one.
Pride and Prejudice: Very enjoyable.
Alice's Adventure in Wonderland is unreviewed.
Adventures of Huckleberry Finn: "My favorite book."
Pride and Prejudice: "Very enjoyable."
Grimms' Fairy Tales: "Masterpiece."

Example 2: Rust HashMap Example

Learn about hashmap in Rust using this example.

Step 1: Create Project

  1. Open your Rust IDE.
  2. In the menu go to File --> Create New Project.

Step 2: Add Dependencies

Go to your Cargo.toml and modify it as follows:

[package]
name = "_11_collections"
version = "0.1.0"
authors = ["Inanc Gumus <[email protected]>"]
edition = "2018"

[dependencies]

Step 3: Write Code

Next create a file known as hashmaps.rs and add the following code:

//
// Rust's HashMap is an ordinary map type that you might already know about from other langs.
//
// -> They are stored on the heap memory.
//
// So Rust's hash map is:
//
//      HashMap<K, V>
//
// HashMap is a generic type:
//
//  -> Keys are of the type K, and,
//  -> Values are of type V.
//

// first, you need to bring it to this scope.
use std::collections::HashMap;

#[allow(unused)]
pub fn run() {
    // ===================================================================
    // let's create a new hash map.
    // ===================================================================
    let mut reviews = HashMap::new();

    // ===================================================================
    // let's add some game reviews.
    //
    // key   => game name (&str)
    // value => review score (floating-point number)
    // ===================================================================
    reviews.insert("Mass Effect", 7.9);
    reviews.insert("Sam and Max: Hit the Road", 9.5);
    reviews.insert("Beneath a Steel Sky", 9.0);
    reviews.insert("Dune", 8.0);

    {
        // or, you can construct it from these vectors.
        let games = vec!["Mass Effect", "Sam and Max: Hit the Road"];
        let scores = vec![7.9, 9.5];

        // now, all you need is to zip them together into a map.
        let mut reviews: HashMap<_, _> = games.into_iter().zip(scores.into_iter()).collect();
        //                       ^  ^        |                  |
        //                       |_/         v                  v
        //    Guesses the key and value     Keys = &str      Values = float
        //          types automatically  <---------====---------------=====
        //             from the vectors

        // ===================================================================
        // ☝️ into_iter()
        //
        //    Above, each into_iter() takes ownership of a vector.
        //
        //    So you can no longer use the games, or scores.
        //
        //          let _ = games.get(0).unwrap();
        //          let _ = scores.get(0).unwrap();
        //
        // ===================================================================
    }

    // ===================================================================
    // let's access the values inside a map
    // ===================================================================
    let mass_effect = reviews.get("Mass Effect");
    println!(
        "They rated Mass Effect with a score of {:?}.",
        mass_effect.unwrap() // unwrap is not cool, you'll see why below.
    );

    // ===================================================================
    // let's check for the existence of keys
    // ===================================================================

    //
    // first, you're going to learn what happens if you try to access
    // a non-existing key.
    //
    let ping_pong = reviews.get("Ping Pong");

    //
    // uncomment the following line and see what happens:
    //
    // println!(
    //     "They rated Ping Pon with a score of {:?}",
    //     ping_pong.unwrap()
    // );
    //

    // now let's learn how to prevent panicking.
    if let None = ping_pong {
        println!("I don't know anything about the Ping Pong game.");
    }
    // ☝️ remember, if let is syntactic sugar for match.
    //    here, we check if ping_pong is one of the variants of
    //    the Option enum.

    // let's check for an existing game.
    let sam_and_max = "Sam and Max: Hit the Road";
    if let Some(score) = reviews.get(sam_and_max) {
        println!(
            "But they also rated {} with a score of {:?}.",
            sam_and_max, score,
        );
    }
    // ☝️ here, we check if there is "some" value in the value returned
    //    by the reviews.get(sam_and_max).
    //
    //    if there is, Rust binds it to the score variable.

    // ===================================================================
    // let's iterate over each key/value pair
    // ===================================================================
    for (key, value) in &reviews {
        println!("-> They rated {} with a score of {}.", key, value);
    }
    // ☝️ remember: borrow, do not unnecessarily own.
    //
    //    if you've used reviews above instead of &reviews,
    //    then you'd be owning, not borrowing.
    //
    //    so, you would no longer be able to use reviews.
    //    it would have been dropped after the loop ends.

    // ===================================================================
    // let's overwrite a value
    // ===================================================================
    // you can overwrite a value if you re-insert with the same key again.
    reviews.insert("Mass Effect", 8.5);
    // let's pretty print everything in the map.
    println!("{:#?}", reviews);

    // ===================================================================
    // let's insert a value only if there isn't a key with the same name
    // ===================================================================
    reviews.entry("Ping Pong").or_insert(6.0);
    // adds Ping Pong with a score of 6.0

    reviews.entry("Ping Pong").or_insert(8.0);
    // has no effect, reviews has Ping Pong

    println!("{:#?}", reviews);

    // ===================================================================
    // let's create a program to find word frequencies in a str
    // ===================================================================
    let text = "that that exists exists in that that that that exists exists in";

    let mut freqs = HashMap::new();

    for w in text.split_whitespace() {
        // split_whitespaces() returns an iterator that returns string sub-slices
        // from text. they don't re-allocate. rust is as efficient as it goes.

        let c = freqs.entry(w).or_insert(0);
        // c            -> becomes 0 if w key doesn't exist on the map.
        // entry(w)     -> returns the entry of a key from the hash map.
        // or_insert(0) -> if the key was absent, inserts the key w
        //                 with a value of 0.

        *c += 1;
        // otherwise:
        // -> or_insert() returns the existing entry in the map
        //    as a mutable reference (&mut i32).
        // -> so you need to dereference it to get the i32 value.
        // -> finally, we can increment it.
    }
    // since each c goes out of scope after the loop's scope, the map's
    // values can be used afterwards. otherwise, this wouldn't be possible.
    // remember, rust doesn't allow immutable & mutable borrowers co-exist.

    println!("{:?}", freqs);
}

Next create a file known as main.rs and add the following code:

// https://doc.rust-lang.org/std/collections/index.html

mod vectors; // make vectors.rs act like the vectors module.

mod strings; // make strings.rs act like the strings module.

mod hashmaps; // make hashmaps.rs act like the hashmaps module.

fn main() {
    println!("*******************************************************************");
    println!("★ VECTORS ★");
    println!("*******************************************************************");
    vectors::run();
    println!("\n");
    println!("*******************************************************************");
    println!("★ STRINGS ★");
    println!("*******************************************************************");
    strings::run();
    println!("\n");
    println!("*******************************************************************");
    println!("★ HASH MAPS ★");
    println!("*******************************************************************");
    hashmaps::run();
}

Next create a file known as strings.rs and add the following code:

// ===================================================================
// 🤯 OMG
// I had a hard time learning through all of these String types.
// When I try to do the collection exercises, I couldn't know what to do.
// So I tried to reread the documentation, and also a lot of posts, etc.
// I think I finally came to understand the necessary knowledge to go on.
// ===================================================================

// ===================================================================
// ☝️ Rust wants you to put more thought when working with Strings.
//    So that you'll be saved from string/character bugs later on.
// ===================================================================

// ===================================================================
// ☝️ When Rustaceans refer to "strings" in Rust,
//    they usually mean the String and the string slice &str types,
//    not just one of those types.
// ===================================================================

// ===================================================================
// String Terminology used in Rust:
//
// ❤️ TLDR:
//
//      -> Use String: If you need an owned and mutable string data.
//      -> use &str  : If you need a borrowed, and read-only string data.
//
// ⭐️ "a string literal like this one"
//
//      -> A fix-sized UTF-8 encoded string slice that refers to a
//         hardcoded location in memory.
//      -> Underlying type: &'static str
//      -> 'static means the value is hardcoded into the binary.
//
// ⭐️ &str
//
//      -> Preferred way to pass strings around.
//      -> Called a string slice.
//      -> It gets copied (not cloned).
//
//      -> UTF-8 encoded: It's a reference to a UTF-8 byte array.
//
//      => Two-words fat pointer:
//      -> A pointer to a str.
//      -> The str's length.
//      -> See: https://doc.rust-lang.org/std/primitive.str.html#representation
//
//      => Size is only known at runtime.
//
//        -> Following won't work because the size is unknown at compile-time.
//           Rust needs to know the size of every variable.
//
//           let impossible: str = "nope";
//
//        -> This will work because &str is a reference to a location
//           in memory. So its address can be known at runtime.
//
//           let possible: &str = "yep";
//
// ⭐️ String
//
//      -> Dynamic string type: Growable, and shrinkable.
//      -> Owned, mutable, UTF-8 encoded, and heap-allocated.
//
//      -> Its source code looks like this:
//
//         pub struct String {
//             vec: Vec<u8>,
//         }
//
//      => You can pass it as &String to a function that accepts &str.
//         WHY?
//         https://doc.rust-lang.org/std/string/struct.String.html#deref
//
//
//             let s = String::from("hey");
//
//             fn p(s: &str) {
//                 println!("{}", s);
//             }
//
//             p(&s);
//               ^
//               |
//      ________/
//      \ Above, Rust automatically does this:
//             &*s
//             ^^
//             ||
//             |+--> Dereferences to str
//             +--> Borrows it
//
//             So it becomes a &str that points to the contents of s.
//
// ⭐️ Other String Types
//
//      -> OsString, OsStr, CString, and Cstr.
//      -> Other crates can create their own string types
//         to encode data, or represent data in memory
//         in different ways.
// ===================================================================

#[allow(unused)] // see: https://kutt.it/Qh9Jfb
pub fn run() {
    // ------------------------------------------------------------------------
    // let's create a new empty String
    // ------------------------------------------------------------------------
    let mut s = String::new();

    // ------------------------------------------------------------------------
    // let's init another s with data
    // ------------------------------------------------------------------------
    let data = "initial data";
    let s = data.to_string(); // converts to a String.
                              // if the type implements the Display trait.

    // ------------------------------------------------------------------------
    // let's init it using a string literal
    // ------------------------------------------------------------------------
    let s = "initial data".to_string();
    // or you can use the from fn, it's the same with the one above
    let s = String::from("initial data");

    // ------------------------------------------------------------------------
    // ☝️ many ops available with Vec<T> are available with String as well.
    // ------------------------------------------------------------------------

    // ------------------------------------------------------------------------
    // updating
    // ------------------------------------------------------------------------
    let mut s = "hello".to_string();
    let s2 = " world";
    s.push_str(s2); // push_str mutably borrows s2
    s += ", how are you"; //        ___________/
                          //       /
    s.push('?'); //               v
    println!("s: {} - s2: {}", s, s2); // so you can still use it

    // ------------------------------------------------------------------------
    // let's concat two strings
    // ------------------------------------------------------------------------
    let hello = "hello".to_string();
    let world = " world!".to_string();
    //
    // hello moves below, so it cannot be used again.
    //
    // this is because, the op below uses the add method of hello.
    // that method takes ownership of hello, and borrows world.
    //
    let hello_world = hello + &world;
    //
    // that's why you can no longer use hello.
    //
    //      println!("{} {}", hello, world); // error: 0382
    //
    // this happens in the name of efficiency.
    // -> add method COPIES world to hello's buffer.
    // -> so it REUSES hello's buffer to prevent creating a new string
    //    each time you concat a string to it.

    // ------------------------------------------------------------------------
    // let's combine multiple strings (2+) using format!()
    // ------------------------------------------------------------------------
    // -> format doesn't take any ownership.
    // -> it just prints the contents to the screen.
    // -> it doesn't make any concatenation.
    let tic = "tic".to_string();
    let tac = "tac".to_string();
    let toe = "toe".to_string();
    let tic_tac_toe = format!("{}-{tac}-{}", tic, toe, tac = tac);
    // let tic_tac_toe = format!("{}-{}-{}", tic, tac, toe); // same as above
    println!("{}", tic_tac_toe);

    // ------------------------------------------------------------------------
    // what about utf-8?
    // ------------------------------------------------------------------------
    // rust counts how many bytes needed to represent a UTF-8 string.
    // -> for example: ü and ı are 2 bytes each.
    println!("len(\"Gunaydin\")           : {} bytes", "Gunaydin".len()); // 8 bytes
    println!("len(\"Günaydın\")           : {} bytes", "Günaydın".len()); // 10 bytes

    // let's count the characters (scalar values) instead.
    println!(
        "\"Günaydın\".chars().count(): {} chars",
        "Günaydın".chars().count()
    ); // 8

    // ------------------------------------------------------------------------
    // let's index a string, think twice.
    // ------------------------------------------------------------------------
    // 🦀 TLDR: "string indexing is a bad idea!"
    //
    // Example:
    //
    //      "Günaydın"[1]
    //
    // ü is two bytes, so why do you want the first byte? it doesn't make sense.
    // bailout!
    //
    // But, why?
    //
    // -> the return type isn't clear: a char? a grapheme cluster? idk.
    // -> to prevent unexpected values.
    // -> to prevent possibly-buggy code.
    // -> to make it possible to guarantee O(1) performance.
    //    -> string indexing is usually O(1).
    //    -> however, often that may not be true for multiple-byte chars.
    // -> to leave the interpretation and storing raw string data to you, the programmer.
    //

    // So what should you do?
    // -> Use proper ranges to get a slice.
    // -> For example: ü is consisting of 2 bytes, within this range: 1..3
    let s = "Günaydın";
    println!("Günaydın[0..1] = {}", &"Günaydın"[0..1]); // G
    println!("Günaydın[1..3] = {}", &"Günaydın"[1..3]); // ü
                                                        // ⭐️ Remember: & is for borrowing.

    // PANIC ERROR: 1..2 is the first byte of ü.
    // -> Rust can't give you a slice like that.
    // println!("Günaydın[0..1] = {}", &"Günaydın"[1..2]);

    // ------------------------------------------------------------------------
    // let's iterate
    // ------------------------------------------------------------------------
    let s = "Günaydın";
    for c in s.chars() {
        println!("{}", c);
    }
    // Behind the scenes: s.chars() calls s.as_bytes().iter() {}

    // If you want to see the individual bytes, use the s.bytes() instead.
    for b in s.bytes() {
        println!("{}", b);
    }

    //
    // What does Rust iterate on with the chars() method?
    //
    // -> chars() iterates over Unicode scalar values.
    //    😡 Is it weird? Kinda.
    //
    //    -> Iteration over grapheme clusters may be what you actually want.
    //    -> This functionality is not provided by Rust's standard library,
    //    -> Check crates.io instead.
    //
    // What's a grapheme cluster?
    //
    // -> you can usually think of it as an ordinary alphabet letter.
    // -> a character (_as we call it_) is a very different thing in the eyes of a computer.
    //
    // Not convinced?
    //
    // read this one: http://www.unicode.org/reports/tr29/#Grapheme_Cluster_Boundaries
    // and this     : https://en.wikipedia.org/wiki/Character_(computing)
    //
}

// REFERENCES:
// https://doc.rust-lang.org/book/ch08-02-strings.html
// https://doc.rust-lang.org/std/primitive.str.html
// https://doc.rust-lang.org/std/string/struct.String.html
// https://doc.rust-lang.org/src/alloc/string.rs.html
// https://doc.rust-lang.org/src/core/str/mod.rs.html

Next create a file known as vectors.rs and add the following code:

// ⭐️ A vector is stored on the heap memory.

pub fn run() {
    //
    // let's create a vector of 32-bit ints:
    //                __________/
    //               /
    let mut v: Vec<i32> = Vec::new();
    //
    // let's create the same vector w/type-inferring:
    //
    //      let v = vec![1, 2, 3];
    //
    // let's create another i32 vector that looks like this: [1, 1, 1].
    //
    //      let v = vec![1; 3];
    //

    // let's update the vector
    v.push(1);
    v.push(2);
    v.push(3);

    println!("\nreading a vector:");
    println!("first : {}", &v[0]);
    println!("second: {}", v.get(1).unwrap()); // use this with match.

    //
    // Borrowing and Ownership:
    //
    // let first = &v[0];              // immutable borrow
    // v.push(4);                      // mutable borrow   -> ERROR
    // println!("first: {}", first);   // immutable borrow
    //
    // A vector can move its elements (behind the scenes).
    // That's why even borrowing the first element, then pushing
    // another one at the end can create an error here.
    //

    println!("\nwalking over a vector:");
    for i in &mut v {
        // v is a Vec<i32>
        // i is a &mut i32, and *i is an i32.
        // Here, we're writing to the memory cell where i lives by dereferencing.
        *i *= 2; // write to the vector element

        println!("{}", i); // read the vector element
    }
    // To walk over immutably, use:
    // for i in &v {..}
}
// Rust will drop v here, along with its elements.

Run

Copy the code, build and run.

Reference

Here are the reference links:

Number Link
1. Download Example
2. Follow code author
3. Code: Apache 2.0 License

Leave a Reply

Your email address will not be published. Required fields are marked *