Understanding HashSet in Rust
Dipankar Paul
Posted on April 22, 2024
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;
You can create a HashSet
with the new
method and make it mutable for modifications.
let mut my_set = HashSet::new();
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();
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);
}
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);
}
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");
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);
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");
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);
}
}
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);
});
}
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);
}
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);
}
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);
}
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);
}
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.
Posted on April 22, 2024
Join Our Newsletter. No Spam, Only the good stuff.
Sign up to receive the latest update from our blog.