Hello! Today, we are going to explore HashSets, a powerful data structure in Rust that belongs to the collections
module. HashSets provide us with an efficient way to store and manage unique items. As we delve into this lesson, you'll learn how to create, manipulate, and leverage the power of HashSets
to solve common programming problems.
Rust's HashSet
is an unordered collection that uses a hash function to manage its elements, ensuring that each element is unique. This makes HashSets
incredibly useful for tasks where you need to check for membership, eliminate duplicates, or perform set operations. Let's get started!
In Rust, creating a HashSet
involves using the HashSet
struct from the std::collections
module. You can either create an empty HashSet
and then add elements to it or create a Hashset
with default values.
Here's how to create a HashSet
:
Rust1use std::collections::HashSet; 2 3fn main() { 4 // Create an empty HashSet 5 let mut empty_set: HashSet<i32> = HashSet::new(); 6 let mut set = HashSet::from([1,2,3,4]); 7}
HashSet
struct from the std::collections
module.HashSet
named empty_set
, which can store i32
values.HashSet
named set
, which already contains some values.Once you have a HashSet
, you can add or remove elements using the insert
and remove
methods.
Rust1use std::collections::HashSet; 2fn main() { 3 let mut hashset: HashSet<i32> = HashSet::new(); 4 5 // Add values to HashSet 6 hashset.insert(1); 7 hashset.insert(2); 8 hashset.insert(3); 9 10 // Remove values from HashSet 11 hashset.remove(&2); 12}
insert
method adds a value to the HashSet
. If the value already exists, it will not be added again.remove
method removes a value from the HashSet
, if it exists. The value passed into remove
must always be a reference.One of the key advantages of using a HashSet
is the ability to quickly check if an item exists within the set. You can also check the length of the HashSet
and whether it's empty.
Rust1use std::collections::HashSet; 2fn main() { 3 let mut hashset: HashSet<i32> = HashSet::new(); 4 5 hashset.insert(1); 6 hashset.insert(3); 7 8 // Check membership in HashSet 9 let has_one = hashset.contains(&1); 10 let has_two = hashset.contains(&2); 11 println!("HashSet has 1: {}, has 2: {}", has_one, has_two); // Prints: "HashSet has 1: true, has 2: false" 12 13 // len() - get the number of elements 14 let length = hashset.len(); 15 println!("Length of HashSet: {}", length); // Prints: "Length of HashSet: 2" 16 17 // is_empty() - check if the set is empty 18 let is_empty = hashset.is_empty(); 19 println!("Is HashSet empty: {}", is_empty); // Prints: "Is HashSet empty: false" 20}
contains
method checks whether a value exists in the HashSet
and returns a boolean. contains
always expects a reference as an input.len
method returns the number of elements in the HashSet
.is_empty
method checks if the HashSet
is empty.As with other data structures in Rust, managing ownership and borrowing is crucial when working with HashSets
. Elements added to a HashSet
must adhere to Rust's ownership rules.
Rust1use std::collections::HashSet; 2fn main() { 3 let s = String::from("Hello"); 4 let mut set = HashSet::from([s, String::from("World")]); 5 println!("{}", s); // Causes an error 6}
String
called s
HashSet
set
, containing s
and another String
element.s
to the Hashset
transfers ownership of "Hello" from s
to set
HashSets
can be passed to functions as references or by value. Understanding how to pass HashSets
to functions allows for more modular and reusable code. Unlike tuples
and arrays
, a HashSet
is never copy type, even if the data held in the HashSet
is copy type.
Rust1use std::collections::HashSet; 2fn main() { 3 let mut hashset = HashSet::new(); 4 hashset.insert(10); 5 hashset.insert(20); 6 7 display_hashset_reference(&hashset); 8 println!("After display_hashset_reference: {:?}", hashset); // Prints: "After display_hashset_reference: {10, 20}" 9 10 display_hashset_ownership(hashset); // Ownership moved to display_hashset_ownership 11 println!("After display_hashset_ownership: {:?}", hashset); // Causes error 12} 13 14fn display_hashset_reference(set: &HashSet<i32>) { 15 println!("In display_hashset_reference: {:?}", set); // Prints: "In display_hashset_reference: {10, 20}" 16} 17 18fn display_hashset_ownership(set: HashSet<i32>) { 19 println!("In display_hashset_ownership: {:?}", set); // Prints: "In display_hashset_ownership: {10, 20}" 20}
display_hashset_reference
takes a reference to a HashSet
, so it doesn't take ownership, allowing the HashSet
to remain available after the function call.display_hashset_ownership
takes a HashSet
by value. Even though the elements are a copyable data type (i32
), a HashSet
is not copy type, thus ownership is transferred.Great job! You've learned how to create and manipulate HashSets
in Rust, explored how to add and remove elements, checked for membership and other properties, understood ownership within HashSets
, and passed HashSets
to functions.
HashSets
are a versatile and efficient tool in Rust for managing collections of unique items. By applying these concepts, you can make your Rust programs more effective and sophisticated.
Now it's time to put these concepts into practice. Head over to the practice exercises to reinforce your understanding and get hands-on experience with HashSets
. Happy coding!