Collecting Into Different Types
🎯 In Python, converting between collection types is effortless:
numbers = [3, 1, 2, 1, 3]
unique = set(numbers) # {1, 2, 3}
sorted_list = sorted(unique) # [1, 2, 3]
pairs = [("a", 1), ("b", 2)]
lookup = dict(pairs) # {"a": 1, "b": 2}
from collections import Counter
Counter(["red", "blue", "red"]) # {'red': 2, 'blue': 1}
You call set(), dict(), list(), sorted() — the constructor does the work. In Rust, one method does all of this: .collect(). What it collects into depends on the type annotation.
.collect() — one method, many targets
.collect() consumes an iterator and builds a collection. The type annotation tells Rust which collection to create:
let v: Vec<i32> = (1..=5).collect(); // [1, 2, 3, 4, 5]
use std::collections::HashSet;
let set: HashSet<i32> = (1..=5).collect(); // {1, 2, 3, 4, 5}
let s: String = vec!['h', 'i'].into_iter().collect(); // "hi"
Same iterator, same .collect() call — different results. This is type-directed dispatch: Rust looks at the expected type and calls the right implementation.
You can also use turbofish syntax instead of a type annotation:
let v = (1..=5).collect::<Vec<i32>>();
Both approaches are equivalent. Use whichever reads better in context.
Collecting into HashMap
An iterator of tuples (K, V) can be collected into a HashMap:
use std::collections::HashMap;
let map: HashMap<&str, i32> = vec![("a", 1), ("b", 2)]
.into_iter()
.collect();
This is the Rust equivalent of dict(pairs) in Python. The iterator must yield 2-element tuples — the first becomes the key, the second the value.
Collecting into HashSet for deduplication
HashSet automatically drops duplicates, just like Python's set():
use std::collections::HashSet;
let unique: HashSet<i32> = vec![1, 2, 2, 3, 3, 3]
.into_iter()
.collect();
// {1, 2, 3}
Since HashSet is unordered (like Python's set), you'll often collect to a HashSet, then convert to a sorted Vec if order matters.
The FromIterator trait
What makes .collect() so flexible is the FromIterator trait. Any type that implements FromIterator<T> can be a .collect() target. Vec, HashSet, HashMap, String, BTreeMap — they all implement it. You can even implement it for your own types.
Counting with the entry API
Rust doesn't have a Counter equivalent in the standard library. Instead, you use the entry API on HashMap to build frequency counts:
let mut map = HashMap::new();
let value = map.entry("key").or_insert(0); // inserts 0 if new, returns &mut V
*value += 1; // modify through the mutable reference
.entry(key) returns an entry that's either occupied or vacant. .or_insert(default) inserts the default if the key is new and returns a mutable reference to the value. Combine this with a loop over your data to build frequency counts — Rust's equivalent of Python's Counter or defaultdict(int).
Trait bounds: Hash + Eq
For a type to be used as a HashMap key or HashSet element, it must implement Hash (for hashing) and Eq (for equality). In Python, objects need __hash__ and __eq__ — same concept, different syntax. Rust's built-in types (i32, String, &str, etc.) all implement both.
When you write generic functions that work with hash-based collections, you'll specify these as trait bounds:
fn do_something<K: Hash + Eq>(key: K) { /* ... */ }
This tells the compiler: "K can be any type, as long as it's hashable and comparable."
Login to see the full task and start coding.
This is a premium exercise
Log in to unlock the full exercise and start coding.
Login to access this exercise