A common shape change
You've been working with a Vec of records. Maybe it came from parsing a CSV, or from an API response, or from a database query. The data is already grouped as key-value pairs, and now you need fast lookups by key. You don't want to scan the whole Vec every time you ask "what's the value for X?", which is what a Vec would force you to do. You want a HashMap.
Going from Vec to HashMap is one of those tasks where Rust's standard library makes you feel clever. It's a single line. The trick is to know that the iterator system has built-in support for collecting into any type that implements the FromIterator trait, and HashMap<K, V> implements it for any iterator of (K, V) tuples.
The smallest version
If your Vec already holds tuples, you're nearly done:
use std::collections::HashMap;
fn main() {
// Each tuple is interpreted as (key, value) when collected into a HashMap.
let pairs = vec![("apple", 3), ("banana", 7), ("cherry", 12)];
// into_iter() consumes the Vec, yielding owned tuples (no borrowing).
// collect() asks: "what should I build?" The type annotation tells it.
let map: HashMap<&str, i32> = pairs.into_iter().collect();
println!("How many bananas? {}", map["banana"]);
}
That's the entire transformation. into_iter consumes the Vec and gives back its elements one by one. collect calls HashMap::from_iter, which loops through inserting each tuple. The type annotation on map is the only thing telling the compiler which collection to build, because collect is generic over the destination.
If you skip the annotation, the compiler will get angry and ask you to specify:
error[E0282]: type annotations needed
--> src/main.rs:5:9
|
5 | let map = pairs.into_iter().collect();
| ^^^
|
help: consider giving `map` an explicit type
|
5 | let map: HashMap<&str, i32> = pairs.into_iter().collect();
| +++++++++++++++++++++
You can also put the type on collect itself with the turbofish syntax: collect::<HashMap<&str, i32>>(). Equivalent, slightly noisier, useful when you want to chain more methods.
When you have a Vec of structs, not tuples
In real code, your Vec is rarely already shaped as tuples. More likely you have something like:
struct User {
id: u64,
name: String,
email: String,
}
And you want a HashMap<u64, User> keyed by ID. The transformation is one map call away:
use std::collections::HashMap;
fn main() {
let users = vec![
User { id: 1, name: "Ada".into(), email: "ada@example.com".into() },
User { id: 2, name: "Lin".into(), email: "lin@example.com".into() },
];
// We pluck the id out as the key and move the whole User into the value.
// Note that this consumes `users`; we couldn't use it again afterwards.
let by_id: HashMap<u64, User> = users
.into_iter()
.map(|u| (u.id, u))
.collect();
println!("{}", by_id[&1].name);
}
If you only want to keep, say, the name keyed by ID and don't care about the rest, just project differently inside the closure:
let names: HashMap<u64, String> = users
.into_iter()
.map(|u| (u.id, u.name)) // u.email is dropped here, no clone needed
.collect();
What about duplicate keys?
HashMap only stores one value per key. If your input has duplicates, the later entry wins:
let pairs = vec![("a", 1), ("b", 2), ("a", 99)];
let map: HashMap<&str, i32> = pairs.into_iter().collect();
// map["a"] == 99
That might be exactly what you want (latest value wins) or it might silently drop data. If you need to combine values for duplicate keys instead, you can fold manually:
use std::collections::HashMap;
let entries = vec![("a", 1), ("b", 2), ("a", 99)];
// fold builds up the map step by step. Each iteration calls or_insert(0)
// to ensure the entry exists, then adds the new value to it.
let totals = entries.into_iter().fold(HashMap::new(), |mut acc, (k, v)| {
*acc.entry(k).or_insert(0) += v;
acc
});
// totals["a"] == 100
entry(k).or_insert(0) is the idiomatic "get the slot for this key, creating it as 0 if absent" pattern. Returning the dereferenced mutable reference lets you += straight into the slot.
Owned versus borrowed keys
The example above uses &str as the key, which is a borrowed reference into the original strings. Sometimes that's fine. Sometimes you want owned String keys so the map outlives whatever produced the data. The fix is to clone or into inside the map:
let pairs = vec![("a", 1), ("b", 2)];
// Convert the &str to String at collection time so the HashMap owns its keys.
let map: HashMap<String, i32> = pairs
.into_iter()
.map(|(k, v)| (k.to_string(), v))
.collect();
The cost is one allocation per key. For tiny tables that doesn't matter. For tables of millions of entries you'd want to reuse the existing Strings if you can.
Going the other direction
HashMap to Vec is just as easy and worth knowing for completeness. HashMap::into_iter yields (K, V) tuples:
let map: HashMap<&str, i32> = HashMap::from([("a", 1), ("b", 2)]);
// Vec of (&str, i32) tuples. Iteration order is not deterministic for HashMap.
let v: Vec<(&str, i32)> = map.into_iter().collect();
If you want sorted output, sort after collecting, or use BTreeMap instead. BTreeMap keeps keys in order at the cost of slightly slower lookups.
Common pitfalls
Forgetting the type annotation. The most frequent mistake. collect is generic, so without a hint it cannot pick a target. Add either a type on the binding or a turbofish.
Trying to collect borrowed tuples. vec![(&"a", &1)].iter().collect::<HashMap<&&str, &i32>>() works but produces a map of references, which is rarely what you want. Use into_iter (consumes) or chain .map(|(k, v)| (k.clone(), v.clone())) if you have to keep the original Vec.
Hash key trait requirements. Your key type must implement Hash and Eq. All built-in types do. For your own structs, derive both:
#[derive(Hash, Eq, PartialEq)]
struct ProductId(u64);
Skip those and you'll get an error like:
error[E0277]: the trait bound `ProductId: Hash` is not satisfied
Floats as keys. f32 and f64 deliberately do not implement Eq because NaN != NaN. If you really need a float-keyed map, wrap the value in something like ordered_float::OrderedFloat.
When to reach for what
Use Vec.into_iter().collect() when you have a one-shot transformation and you don't need to reuse the original Vec. Use iter().cloned().collect() when you want to keep both. Use fold with entry().or_insert() when keys may collide and you want to merge values. Use BTreeMap instead of HashMap when you want sorted iteration order.
For larger pipelines that touch multiple collections, the itertools crate adds helpers like into_group_map, which collects directly into a HashMap<K, Vec<V>> and saves you the manual fold.