DEV Community

DIPANKAR PAUL
DIPANKAR PAUL

Posted on

Understanding HashSet in Rust

In Rust, a HashSet is a collection that stores unique elements in no particular order, using a hash-based implementation for efficient membership tests. Essentially, a HashSet is similar to a HashMap where the value is () (unit type), making it a HashMap with keys and no values.

Creating a HashSet

To use a HashSet, you need to include the HashSet module from the standard collections library.

use std::collections::HashSet;
Enter fullscreen mode Exit fullscreen mode

You can create a HashSet with the new method and make it mutable for modifications.

let mut my_set = HashSet::new();
Enter fullscreen mode Exit fullscreen mode

By default, the type is inferred based on the inserted values, but you can also specify types explicitly.

let mut my_set: HashSet<String> = HashSet::new();
Enter fullscreen mode Exit fullscreen mode

We can also create a hashset with default values using the from() method when creating it.

use std::collections::HashSet;

fn main() {
    // Create HashSet with default values using from() method
    let numbers1 = HashSet::from([2, 7, 8, 10]);

    println!("numbers = {:?}", numbers);
}
Enter fullscreen mode Exit fullscreen mode

You can also turn a Vec into a HashSet using the into_iter() method(consumes ownership), followed by the collect() method.

use std::collections::HashSet;

fn main() {
    // Turning a vector into a HashSet
    let my_vec: Vec<i32> = vec![1, 2, 3, 4, 5];
    let my_set: HashSet<_> = my_vec.into_iter().collect();

    println!("{:?}", my_set);
}
Enter fullscreen mode Exit fullscreen mode

Note that the type of the HashSet is inferred using the _ placeholder, but you can explicitly specify the type if needed.

Adding Elements to a HashSet

Elements can be added using the insert method, ensuring uniqueness within the set.

my_set.insert("apple");
my_set.insert("banana");
my_set.insert("orange");
Enter fullscreen mode Exit fullscreen mode

Note: Adding a new value to the hashset is only possible when you declare variable as mut.

Printing a HashSet

To print the contents of a HashSet, use the println! macro with {:?} to display the elements.

println!("{:?}", my_set);
Enter fullscreen mode Exit fullscreen mode

Difference Between HashSet and BTreeSet

In Rust, there are different types of sets available. HashSet uses a hash table for fast access, suitable for unordered data. On the other hand, BTreeSet uses a binary search tree, maintaining elements in sorted order.

Accessing, Removing, and Updating Elements

You can check if an element is present using contains, remove elements with remove, and update elements with replace.

if my_set.contains("apple") {
    println!("The set contains 'apple'");
}

my_set.remove("banana");

my_set.replace("apple", "grape");
Enter fullscreen mode Exit fullscreen mode

Iterating over Values of a HashSet

There are several ways to iterate over the values of a HashSet, depending on whether you need references, mutable references, or want to consume and take ownership of the elements.

Iterating by References (Immutable Borrow)

Using an immutable reference to iterate over the values of a HashSet.

use std::collections::HashSet;

fn main() {
    let my_set: HashSet<i32> = vec![1, 2, 3, 4, 5].into_iter().collect();

    for value in my_set.iter() {
        println!("Reference to: {}", value);
    }
}
Enter fullscreen mode Exit fullscreen mode

Here, the iter method is used to obtain an iterator over references to the elements.

Using method chaining with closures,

use std::collections::HashSet;

fn main() {
    let my_set: HashSet<i32> = vec![1, 2, 3, 4, 5].into_iter().collect();

    // Using .iter().for_each() to print each element
    my_set.iter().for_each(|&value| {
        println!("Reference to: {}", value);
    });
}
Enter fullscreen mode Exit fullscreen mode

Here, he closure takes an immutable reference to each element in the HashSet and prints it. These methods doesn't consume the elements, and ownership remains with the HashSet.

Iterating by Mutable References (Mutable Borrow)

use std::collections::HashSet;

fn main() {
    let mut my_set: HashSet<i32> = vec![1, 2, 3, 4, 5].into_iter().collect();

    for value in my_set.iter_mut() {
        *value *= 2;  // Doubling each element in-place
    }

    println!("{:?}", my_set);
}
Enter fullscreen mode Exit fullscreen mode

Here, the iter_mut method is used to obtain a mutable iterator, allowing modifications of the elements in the set.

Using method chaining with closures,

use std::collections::HashSet;

fn main() {
    let mut my_set: HashSet<i32> = vec![1, 2, 3, 4, 5].into_iter().collect();

    my_set.iter_mut().for_each(|value| {
        *value *= 2;
    });

    println!("{:?}", my_set);
}
Enter fullscreen mode Exit fullscreen mode

Here, the closure takes a mutable reference to each element in the HashSet and doubles its value in-place. This allows modification of the elements in-place while still keeping ownership with the HashSet.

Consuming Iteration (Ownership Transfer)

Using the into_iter method to consume the elements of the HashSet.

for value in my_set.into_iter() {
    println!("Owned value: {}", value);
}
Enter fullscreen mode Exit fullscreen mode

Here, into_iter is used to obtain an iterator that consumes the HashSet. The type of value is i32, indicating ownership transfer.

Using a for loop directly on the HashSet is concise but it will also consume and transfer ownership.

for value in my_set {
    println!("Owned value: {}", value);
}
Enter fullscreen mode Exit fullscreen mode

Conclusion

HashSet in Rust provides a powerful way to store unique elements efficiently. Understanding how to create, modify, and iterate over a HashSet is essential for working with collections in Rust.

Top comments (0)