Start learning free

LESSON 241 OF 679 · SETS

Removing From a Set in Rust

Lesson Read the full explanation

TL ; DR:

remove(&x) takes x out of a set. A for loop over &s visits every value once, in no fixed order.


let mut colors: HashSet<&str> = vec!["red", "blue", "green"].into_iter().collect();

colors.remove("blue");

colors.remove("pink");          // not in it, nothing happens

for color in &colors {
    println!("{}", color);      // red and green, in any order
}


Full lesson:

s.remove(&x) removes x from the set s. It returns true if x was there. If it wasn't, nothing happens and it returns false.

Like contains, remove takes a reference. A string literal like "pink" already is one. For a number, write s.remove(&5).

for x in &s visits each value of the set once. The & borrows the set, so you can still use it after the loop.

The order isn't fixed. Rust may even change it from one run to the next. So loop over a set when the order doesn't matter. A total, a count and a check are fine.

Don't remove values from a set while you loop over it. The compiler refuses, because the loop is borrowing the set. Remove first, then loop.


Your task

In a word game, the set found holds the words a player found. The words "qqq" and "zzz" aren't allowed. Remove both from found.

The player never found "zzz", so removing it changes nothing.

Then add up the points. A word scores one point per letter. Loop over found and add each word's length to score.

The code already prints how many words are left, and the points.

Expected output:

4 words
14 points
STEP 1 OF 3
    Ctrl + Enter to check

    RUST · SETS

    Removing From Sets in Rust, explained

    remove and looping

    A HashSet can lose values as well as gain them. s.remove(&x) takes x out of the set s. It returns true if x was there, and false if it wasn't, in which case nothing happens and nothing goes wrong:

    let mut found: HashSet<&str> = HashSet::from(["cat", "qqq"]);
    println!("{}", found.remove("cat"));
    println!("{}", found.remove("cat"));
    

    This prints true, then false: the second call finds nothing to remove. (Use use std::collections::HashSet; in a file of your own.)

    Like contains, remove takes a reference. A string literal already is one, so remove("qqq") works. For a number, write the & yourself: n.remove(&2).

    To visit every value, loop over a borrowed set:

    for color in &colors {
        println!("{}", color);
    }
    

    The & borrows the set, so it is still yours after the loop. Without it, the loop would take the set away.

    The order is not fixed

    A set has no order, and Rust may even change it from one run to the next. Loop over a set when the order doesn't matter: a total, a count or a check are all fine.

    Don't remove while looping

    Removing from a set inside a loop over that same set does not compile. The loop is borrowing the set, and remove needs to change it:

    for word in &found {
        if word.len() == 3 {
            found.remove(word);
        }
    }
    
    error[E0502]: cannot borrow `found` as mutable because it is also borrowed as immutable
    

    The fix is to do the removing first and the looping after, which is exactly what the lesson's task does.

    Using the answer of remove

    You can ignore what remove returns, as the task does, or use it as a question: "was this value in the set?" For a set of numbers, HashSet::from([1, 2, 3]) followed by n.remove(&2) gives true, and the set now holds 1 and 3. This saves a separate contains call when you want to take a value out only if it is there, and to know whether it was.

    The lesson's task

    In a word game, the set found holds the words a player found. The words "qqq" and "zzz" aren't allowed, so both must be removed. The player never found "zzz", so removing it changes nothing. Then each word scores one point per letter: loop over found and add each word's length to score.

    use std::collections::HashSet;
    
    fn main_function() {
        let words = vec!["cat", "tree", "sun", "qqq", "moon"];
        let mut found: HashSet<&str> = words.into_iter().collect();
    
        found.remove("qqq");
        found.remove("zzz");
        println!("{} words", found.len());
    
        let mut score = 0;
        for word in &found {
            score += word.len();
        }
        println!("{} points", score);
    }
    

    The output is:

    4 words
    14 points
    

    Five words went in. "qqq" was removed, "zzz" was never there, so four words remain: cat, tree, sun and moon. Their lengths are 3 + 4 + 3 + 4 = 14. The total is the same whichever order the loop visits the words in.

    Notice that word.len() is the length in bytes. For these plain English words that is the number of letters; for a word such as "été" it is 5, while it has 3 letters.

    Because remove only needs the value, a repeat call is harmless: removing the same word twice, or a word that was never added, leaves the set as it is.

    Key takeaways

    • remove(&x) returns true if the value was there and false if not; it never fails.
    • It takes a reference, like contains.
    • for x in &s visits each value once, in no fixed order.
    • Remove before or after a loop over the set, not inside it.
    Keep going with Rust500+ step-by-step lessons, from your first line of code to coding interviews. Free to start.
    Start learning free