Mục lục
Mục Tiêu Bài Học
Sau bài học, bạn sẽ:
- Biết viết
for (k, v) in &mđể duyệt qua mọi entry trongHashMapmà không move map. - Phân biệt
.keys(),.values(),.values_mut()— khi nào dùng từng method. - Dùng
.iter_mut()để sửa giá trị in-place mà không cần tra cứu lại qua key. - Hiểu
.into_iter()consume map, yield cặp(K, V)owned — cần khi muốn move giá trị ra ngoài. - Nhận ra rằng thứ tự duyệt là random per process theo thiết kế bảo mật, không thể dựa vào trong test/output.
- Áp dụng workaround
collect+sort_by_keyhoặc chuyển sangBTreeMapkhi cần ordering ổn định.
for (k, v) in &m — Iterate Borrow
Form thường gặp nhất: duyệt qua toàn bộ entry với borrow shared, không move m:
use std::collections::HashMap;
let mut m = HashMap::new();
m.insert("apple", 3);
m.insert("banana", 5);
m.insert("cherry", 2);
for (key, value) in &m {
println!("{key}: {value}");
}
println!("len = {}", m.len()); // m còn nguyên, dùng được
Chi tiết quan trọng:
keykiểu&K(ở ví dụ là&&str),valuekiểu&V(&i32). Đây là vì&HashMapimplementIntoIteratorvớiItem = (&K, &V).mkhông bị move; sau loop bạn vẫn dùngm.len(),m.get(...)... bình thường — đúng như&Vecđã học ở Bài 123.- Form này tương đương method
m.iter():for (k, v) in m.iter() { ... }. Nếu cần chain adapter (.filter,.map), bắt buộc dùng.iter(). - Khi
println!in&i32, traitDisplayauto-deref nên không cần viết*value. Truyền vào hàm nhậni32thuần thì phải dereference.
Đây là default — viết quen tay khi chỉ cần đọc cặp (key, value).
.keys() Chỉ Iterate Key
Khi chỉ cần danh sách key (ví dụ in tên tất cả mặt hàng, build set tên cột, hoặc kiểm tra membership), m.keys() trả về iterator chỉ yield reference đến key:
for k in m.keys() {
println!("- {k}");
}
// Hoặc collect thành Vec để xử lý tiếp:
let names: Vec<&&str> = m.keys().collect();
Một số điểm cần nhớ:
- Signature là
fn keys(&self) -> Keys<'_, K, V>, trả về iterator yield&K. Đây là borrow shared —mkhông bị ảnh hưởng. - Tương đương
m.iter().map(|(k, _)| k)nhưng ngắn và không phải viết tuple destructuring với underscore. - Hay dùng cùng
.collect::<Vec<_>>()để có danh sách key dạngVecrồi sort hoặc filter.
Đối ứng là .into_keys() — consume m, trả iterator yield K owned. Hữu ích khi cần lấy key ra để move sang struct khác và không còn cần m.
.values() / .values_mut() Iterate Value
Đối xứng với .keys(), hai method giúp duyệt phía value:
// Borrow shared, yield &V
for v in m.values() {
println!("count = {v}");
}
// Borrow mut, yield &mut V — sửa từng value in-place
for v in m.values_mut() {
*v += 100;
}
// Tất cả count đã + 100, không cần biết key
Vài lưu ý quan trọng:
.values()trảValues<'_, K, V>yield&V. Tốt cho phép tính tổng:let total: i32 = m.values().sum();..values_mut()trảValuesMut<'_, K, V>yield&mut V. Đây là cách ngắn nhất để update tất cả value mà không cần đi qua từng key bằngm.get_mut(k).mphải khai báolet mut m = ...để có quyền borrow mut.- Trong khi iterator
values_mutcòn sống, không được modify cấu trúc của map (insert/remove) — borrow checker chặn.
Đây cũng là cách thay đổi đồng loạt giá trị mà giữ nguyên key — ví dụ scale tất cả số đếm, đổi đơn vị, hoặc reset về 0 trước batch mới.
.iter_mut() Mutable Reference
Khi cần cả key (để quyết định công thức update) và mutable access đến value, dùng .iter_mut():
let mut m: HashMap<&str, i32> = HashMap::new();
m.insert("apple", 3);
m.insert("banana", 5);
for (k, v) in m.iter_mut() {
if *k == "apple" {
*v *= 2; // chỉ nhân đôi apple
} else {
*v += 1;
}
}
Vài chi tiết cần nắm:
- Iterator yield
(&K, &mut V)— key vẫn là shared reference (không được đổi key giữa chừng vì hash đã placement), value là mutable reference. - Phải dereference
*vđể gán hoặc compound-assign.v = ...chỉ rebind biến local trong loop, không sửa map. - Đây là cách thay thế cho pattern dài dòng
for k in keys { let v = m.get_mut(k).unwrap(); ... }— nhanh hơn (một lần lookup), gọn hơn, và borrow checker thấy rõ scope. mphải làmut. Trong khi iterator còn sống, không thể đụng vàomtheo cách khác.
.into_iter() Consume
Khi cần ownership của cặp key-value — ví dụ chuyển sang container khác, gửi vào channel, hoặc consume sang struct — dùng .into_iter():
let m: HashMap<String, Vec<i32>> = build_map();
let pairs: Vec<(String, Vec<i32>)> = m.into_iter().collect();
// m đã move, không dùng lại được
// Hoặc move sang BTreeMap để có thứ tự:
use std::collections::BTreeMap;
let sorted: BTreeMap<String, Vec<i32>> = m.into_iter().collect();
Một số điểm cần nhớ:
- Iterator yield
(K, V)owned. Bạn có thể move thẳng từng phần tử sang nơi khác, điều mà.iter()(yield(&K, &V)) không cho phép. mbị move; sau loop không thể đụng vào nữa. Compiler sẽ báo "borrow of moved value" nếu bạn thử.- Form
for (k, v) in m(không có&) là syntactic sugar gọi.into_iter()ngầm — kết quả tương đương. - Có thêm
.into_keys()và.into_values()nếu chỉ cần một phía dạng owned.
Quy tắc tóm tắt giống Vec: cần đọc → .iter(); cần sửa value → .iter_mut() hoặc .values_mut(); cần move ra ngoài → .into_iter().
Order Không Deterministic
Điểm rất dễ "vấp" với newcomer: HashMap không bảo đảm thứ tự duyệt khớp với thứ tự insert, và càng không khớp giữa các lần chạy chương trình:
use std::collections::HashMap;
let mut m = HashMap::new();
m.insert("a", 1);
m.insert("b", 2);
m.insert("c", 3);
for (k, v) in &m {
println!("{k}: {v}");
}
// Run 1: c: 3 / a: 1 / b: 2
// Run 2: b: 2 / c: 3 / a: 1
// Run 3: a: 1 / b: 2 / c: 3
Lý do là HashMap mặc định dùng SipHash với một seed sinh ngẫu nhiên mỗi khi process khởi động — biện pháp chống HashDoS, tránh attacker chèn key cố ý gây collision. Kết quả: thứ tự bucket khác nhau mỗi run.
- KHÔNG dựa vào thứ tự duyệt trong test (so sánh output bằng string). Test sẽ flaky.
- KHÔNG in trực tiếp
HashMapra log nếu output cần diff được giữa các run. - Nếu cần thứ tự cố định theo key, dùng
BTreeMap— sẽ học ở bài sau, tự sort theoK: Ord. - Nếu cần thứ tự insert (kiểu Python 3.7+), dùng crate
indexmap::IndexMapngoài stdlib.
Đây là quyết định thiết kế có chủ đích, không phải bug — phải nhớ rõ ngay từ đầu.
Workaround: Collect Rồi Sort
Khi thực sự cần output có thứ tự ổn định mà chưa muốn chuyển hẳn sang BTreeMap, kỹ thuật chuẩn là collect sang Vec rồi sort:
let mut v: Vec<(&&str, &i32)> = m.iter().collect();
v.sort_by_key(|(k, _)| *k);
for (k, val) in v {
println!("{k}: {val}");
}
// Luôn in theo thứ tự alphabetic: a, b, c
Mở rộng — sort theo value (ví dụ ranking) cũng dùng cùng pattern:
let mut v: Vec<(&&str, &i32)> = m.iter().collect();
v.sort_by(|a, b| b.1.cmp(a.1)); // sort giảm dần theo value
for (k, val) in v.iter().take(3) {
println!("Top: {k} = {val}");
}
Ưu nhược điểm:
- Tốn thêm một
Vecvà một lần sort —O(n log n). Với map nhỏ-trung bình không vấn đề. - Linh hoạt: có thể sort theo key, theo value, theo tuple
(k, v)compound. - Nếu cần thứ tự key cố định luôn luôn,
BTreeMaptrực tiếp tốt hơn — không phải collect/sort mỗi lần.
Trong test, idiom let mut keys: Vec<_> = m.keys().collect(); keys.sort(); assert_eq!(keys, vec![...]); là cách an toàn để assert nội dung map mà không phụ thuộc hash seed.
Tổng Kết
- Sáu cách duyệt:
for (k, v) in &m(≡.iter()),.keys(),.values(),.values_mut(),.iter_mut(),.into_iter(). - Borrow:
.iter()yield(&K, &V);.iter_mut()yield(&K, &mut V);.into_iter()yield(K, V)consume map. - Một phía:
.keys()/.values()/.values_mut()ngắn hơn.iter().map(|(k, _)| k). - Order random: thứ tự duyệt khác nhau mỗi process do SipHash seed; không dựa vào.
- Workaround:
let mut v: Vec<_> = m.iter().collect(); v.sort_by_key(|(k, _)| *k);để có ordered iteration. - Cần thứ tự cố định lâu dài: dùng
BTreeMap(sort theo key) hoặc crateindexmap(theo insert order).
Bài Tập Củng Cố
Tự trả lời, đáp án ở cuối:
- Cho
let mut m: HashMap<&str, i32> = HashMap::new();với ba entry "a"=1, "b"=2, "c"=3. Viết loop in tổng tất cả value bằng.values().sum::<i32>(). - Có
let mut scores: HashMap<&str, i32> = ...;. Nhân đôi tất cả score bằng.values_mut()mà không lookup từng key. - Cho
HashMap<String, i32>. Viết loop in từng entry theo dạng"key=value", có thứ tự alphabetic theo key (dùng collect + sort). - Vì sao
for (k, v) in m { ... }rồiprintln!("{:?}", m)báo lỗi? Sửa thế nào nếu chỉ muốn đọc? - Cho hàm
fn to_pairs(m: HashMap<String, i32>) -> Vec<(String, i32)>. Viết body dùng.into_iter().collect(). Tại sao không dùng được.iter()? - Viết test assert map
mchứa đúng ba entry "a"=1, "b"=2, "c"=3 mà không phụ thuộc thứ tự duyệt. Gợi ý: collect key vào Vec, sort, rồi assert.
Đáp án
let total: i32 = m.values().sum(); println!("total = {total}");for v in scores.values_mut() { *v *= 2; }— không cần tra cứu lại bằngget_mut.let mut v: Vec<_> = m.iter().collect(); v.sort_by_key(|(k, _)| (*k).clone()); for (k, val) in v { println!("{k}={val}"); }- Vì
for (k, v) in mngầm gọim.into_iter(), movemvào loop; sau loopmđã mất ownership. Sửa: viếtfor (k, v) in &m. m.into_iter().collect(). Không dùng.iter()vìiter()trả(&String, &i32), không phải owned(String, i32)như signature yêu cầu — collect raVec<(String, i32)>sẽ không match.let mut keys: Vec<&String> = m.keys().collect(); keys.sort(); assert_eq!(keys, vec![&"a".to_string(), &"b".to_string(), &"c".to_string()]); assert_eq!(m.get("a"), Some(&1)); assert_eq!(m.get("b"), Some(&2)); assert_eq!(m.get("c"), Some(&3));
Bài Tiếp Theo
Bài 130: HashMap Với Custom Struct Làm Key — đến giờ bạn đã dùng key là String, &str, i32 — toàn những type stdlib đã implement sẵn Hash + Eq. Bài sau giải thích cần derive những trait gì khi muốn struct của mình làm key, axiom hash phải consistent với Eq, và demo một Point struct làm key cho cache 2D.
