Start learning free

LESSON 237 OF 679 · SETS

Sets in Rust: HashSet Basics

Lesson Read the full explanation

TL ; DR:

A HashSet holds values without repeats. Inserting a value that is already in it changes nothing.


use std::collections::HashSet;

let mut pets: HashSet<&str> = HashSet::new();   // an empty set

pets.insert("cat");

pets.insert("cat");                // already in it, no change

println!("{}", pets.len());        // 1

println!("{}", pets.contains("cat"));   // true


Full lesson:

A Vec keeps every value you put in it, repeats too. Sometimes you only need to know which values there are. A set is made for that.

A set holds each value once. Inserting a value that is already in it changes nothing.

HashSet comes from Rust's standard library. The use line at the top of the sample brings it in. Without it, the compiler doesn't know the name.

  • HashSet::new() makes an empty set. Declare it with let mut to change it.
  • s.insert(x) puts x in the set s. It returns false for a repeat.
  • s.contains(&x) is true when x is in the set. It takes a reference.
  • s.len() gives how many values the set holds.
  • v.into_iter().collect() builds a set from the vector v. Its repeats go in once.

As in Vec & Iterators, collect() needs the type after the name. Write let s: HashSet<&str> = ....

Checking s.contains(&x) is fast, even for a big set. A vector's contains looks at the values one by one.

A set has no order and no indexes. A printed set may show its values in any order.


Your task

The camp's sign-up vector, names, has some names twice. Build a HashSet named kids from it.

Then Zoe signs up, and Ava signs up again. Insert both names into kids.

Last, if kids contains "Leo", print Leo is coming.

The code already prints the number of kids twice.

Expected output:

3 kids
4 kids
Leo is coming
STEP 1 OF 3
    Ctrl + Enter to check

    RUST · SETS

    Sets in Rust, explained

    What a set is

    A Vec keeps every value you put in it, repeats included. Sometimes you only care about which values there are, not how often or in what order. A set is made for that: it holds each value once, and putting in a value it already has changes nothing.

    Rust's set is HashSet. It lives in the standard library, so a file of your own needs use std::collections::HashSet; at the top. Without that line the compiler doesn't know the name.

    The basic operations

    use std::collections::HashSet;
    
    fn main() {
        let mut pets: HashSet<&str> = HashSet::new();
        pets.insert("cat");
        pets.insert("cat");
        println!("{}", pets.len());
        println!("{}", pets.contains("cat"));
    }
    

    This prints 1 and true. The second insert found "cat" already there.

    • HashSet::new() makes an empty set. Declare it with let mut, because adding changes it.
    • s.insert(x) puts x in the set. It returns true if the value was new and false for a repeat.
    • s.contains(&x) is true when x is in the set. It takes a reference, so for a number you write nums.contains(&2). A string literal such as "cat" already is a reference.
    • s.len() is the number of values in the set.

    The return value of insert is handy: after kids.insert("Zoe") on a set that already holds Zoe, the call gives false, and for a new name such as "Kai" it gives true.

    Building a set from a vector

    v.into_iter().collect() turns a vector into a set, and every repeat goes in once. The compiler can't guess the kind of collection you want, so you must write the type:

    let nums: HashSet<i32> = vec![1, 2, 2, 3].into_iter().collect();
    

    This set holds 1, 2 and 3. Without the type, the program does not compile: the compiler reports type annotations needed.

    Gotchas

    • Forgetting mut. Calling insert on a set declared with plain let fails with cannot borrow `kids` as mutable, as it is not declared as mutable.
    • No order, no indexes. A set has no first element and no s[0]. Printing it or looping over it can show the values in any order. To see them in a steady order, collect them into a vector and sort it.
    • Speed. contains on a set is fast even for a big set. On a vector it looks at the values one by one.

    The lesson's task

    A camp's sign-up vector names has some names twice. The task: build a set kids from it, add Zoe and then Ava, and print Leo is coming if Leo is in the set.

    use std::collections::HashSet;
    
    fn main_function() {
        let names = vec!["Ava", "Leo", "Ava", "Mia", "Leo"];
    
        let mut kids: HashSet<&str> = names.into_iter().collect();
        println!("{} kids", kids.len());
    
        kids.insert("Zoe");
        kids.insert("Ava");
        println!("{} kids", kids.len());
    
        if kids.contains("Leo") {
            println!("Leo is coming");
        }
    }
    

    The output is:

    3 kids
    4 kids
    Leo is coming
    

    The vector has five names but only three different ones (Ava, Leo, Mia), so the first count is 3. Zoe is new and raises the count to 4. Ava is already in the set, so inserting her again changes nothing.

    Key takeaways

    • A HashSet holds each value once; inserting a repeat changes nothing.
    • insert, contains and len are the three basics.
    • Build one from a vector with into_iter().collect() and write the set's type.
    • Declare it with let mut if you will add to it.
    • A set has no order and no indexes.
    Keep going with Rust500+ step-by-step lessons, from your first line of code to coding interviews. Free to start.
    Start learning free