Pola pembaruan paling sering muncul di aplikasi data bukanlah “ganti nilai lama”, melainkan “tambah nilai yang mungkin belum ada”. Stok toko bertambah sepuluh unit — kalau produknya belum tercatat, mulailah dari sepuluh. Suara pemilih dihitung satu per satu — nama kandidat yang belum muncul langsung tercatat dengan satu suara. Dengan method yang sudah kita kenal, pola ini menuntut dua langkah: periksa keberadaan key, lalu pecahkan menjadi dua cabang antara mengubah dan menambah. Chapter ini memperkenalkan Entry API — pintu tunggal di HashMap yang menggabungkan pengecekan dan pembaruan dalam satu operasi, sekaligus menjadi idiom Rust yang paling sering memenangkan kontes keanggunan kode.
Masalah: Update yang Tidak Bisa Satu Langkah #
Kita tulis dulu versi manualnya agar masalahnya terlihat. Toko menerima pengiriman barang: Teh sudah ada dengan stok 20, sedangkan Kopi benar-benar baru:
use std::collections::HashMap;
fn main() {
let mut product_stock = HashMap::new();
product_stock.insert(String::from("Teh"), 20);
if let Some(stock) = product_stock.get_mut("Teh") {
*stock += 10;
} else {
product_stock.insert(String::from("Teh"), 10);
}
if let Some(stock) = product_stock.get_mut("Kopi") {
*stock += 10;
} else {
product_stock.insert(String::from("Kopi"), 10);
}
println!("Stok Teh : {:?}", product_stock.get("Teh"));
println!("Stok Kopi: {:?}", product_stock.get("Kopi"));
}Output yang Dihasilkan:
Stok Teh : Some(30)
Stok Kopi: Some(10)Kode ini benar — Teh naik dari 20 ke 30 karena cabang Some yang berjalan, dan Kopi lahir dengan 10 karena cabang else yang berjalan. Harganya pun terbayar jelas: enam baris per produk, get_mut untuk mencari, dereference *stock untuk mengubah, dan insert terpisah untuk kasus key baru. Setiap pembaruan juga melakukan pencarian dua kali ketika key ternyata baru — sekali saat get_mut menjawab None, sekali lagi saat insert mencari posisi penyimpanan. Bayangkan pola ini berulang untuk ratusan produk setiap kali truk pengiriman datang.
Entry API: Satu Pintu untuk Dua Nasib #
Method entry menghapus seluruh percabangan manual tersebut. Versi yang sama kini terlihat seperti ini:
use std::collections::HashMap;
fn main() {
let mut product_stock = HashMap::new();
product_stock.insert(String::from("Teh"), 20);
let tea_stock = product_stock.entry(String::from("Teh")).or_insert(0);
*tea_stock += 10;
let coffee_stock = product_stock.entry(String::from("Kopi")).or_insert(0);
*coffee_stock += 10;
println!("Stok Teh : {:?}", product_stock.get("Teh"));
println!("Stok Kopi: {:?}", product_stock.get("Kopi"));
}Output yang Dihasilkan:
Stok Teh : Some(30)
Stok Kopi: Some(10)Output identik dengan versi manual — dengan enam baris percabangan digantikan dua baris deklaratif. Mari kita bedah mekanismenya baris per baris:
- Baris 7 (
product_stock.entry(String::from("Teh"))):entrymenerima key dan mengembalikan nilai bertipeEntry— sebuah enum dengan dua varian:Occupiedketika key sudah berada di map, danVacantketika slotnya masih kosong. Key dihitung hash-nya tepat satu kali di panggilan ini. - Baris 7 (
.or_insert(0)): methodor_insertmengambil alih kedua varianEntry. VarianOccupieddijawab dengan mengembalikan reference mutable&mut Vke value yang sudah ada — nilai0diabaikan sama sekali. VarianVacantdijawab dengan memasukkan pasangan key dan0, lalu mengembalikan&mut Vke nilai yang baru saja dimasukkan tersebut. - Baris 8 (
*tea_stock += 10;): dereference menambah10ke ujung reference. Karena"Teh"sudah ada,or_insert(0)mengembalikan reference ke20yang lama — dan hasil20 + 10itulahSome(30)di output. Nilai default0tidak pernah menyentuh map. - Baris 10-11 (
entry(String::from("Kopi"))): skenario sebaliknya. Key"Kopi"mendapat varianVacant,or_insert(0)menanam nilai0lebih dulu, barulah*coffee_stock += 10menaikkannya menjadi10.
Bukti terbaik perilaku or_insert justru ada di baris 7: nilai Teh tetap dilanjutkan dari 20, bukan direset ke 0 — or_insert tidak pernah menimpa value yang sudah ada. Berbeda dari insert yang selalu mengganti, or_insert hanya berperan saat slot kosong.
Dari sisi efisiensi, entry selesai dengan satu kali perhitungan hash: varian Occupied dan Vacant membawa informasi posisi internal hasil pencarian pertama, sehingga or_insert tidak mengulang pencarian. Pada versi manual, key yang baru mengalami dua kali pencarian penuh. Kepemilikan juga mengikuti aturan yang sudah kita kenal: key yang dikirim ke entry berpindah ke map — key baru disimpan, sedangkan key yang ternyata sudah ada langsung di-drop setelah perbandingan selesai.
Nilai default tentu tidak harus nol. Penghitungan stok gudang bisa memulai dari jumlah kiriman pertama dengan or_insert(50), pencatatan hadir bisa memulai dari or_insert(1), dan rekap transaksi bisa memulai dari nominal pertama dengan or_insert(amount) — bentuk polanya sama, hanya angka awalnya yang berganti. Yang tidak boleh berganti adalah polanya sendiri: periksa dan tambahkan harus terjadi dalam satu ekspresi. Pola manual contains_key diikuti insert mengundang bug halus ketika kode di antara keduanya mengubah isi map — misalnya pemanggilan fungsi lain yang ikut menyentuh product_stock. Dengan entry, jendela di antara pemeriksaan dan tindakan tidak ada sama sekali, sehingga tidak ada ruang bagi perubahan yang menyelinap.
Pola Klasik: Menghitung Suara #
Kekuatan Entry API paling terasa pada penghitungan frekuensi. Program berikut menghitung suara pemilihan nama maskot kelas dari daftar pilihan yang mentah:
use std::collections::HashMap;
fn main() {
let votes = vec!["Garuda", "Rajawali", "Garuda", "Elang", "Garuda", "Rajawali"];
let mut vote_counts = HashMap::new();
for team in votes {
let count = vote_counts.entry(team).or_insert(0);
*count += 1;
}
println!("Suara Garuda : {:?}", vote_counts.get("Garuda"));
println!("Suara Rajawali: {:?}", vote_counts.get("Rajawali"));
println!("Suara Elang : {:?}", vote_counts.get("Elang"));
}Output yang Dihasilkan:
Suara Garuda : Some(3)
Suara Rajawali: Some(2)
Suara Elang : Some(1)Mari kita telaah jantung programnya:
- Baris 4 (
let votes = vec![...]): daftar suara mentah berisi enam suara dengan duplikasi — Garuda tiga kali, Rajawali dua kali, Elang sekali. Loopfor team in votesmemindahkan setiap&strkeluar dari vector, satu suara per putaran. - Baris 8 (
let count = vote_counts.entry(team).or_insert(0);): suara pertama untuk sebuah nama menghasilkan varianVacant— counter lahir dari0. Suara berikutnya dengan nama yang sama menghasilkan varianOccupied— counter lama dipinjam secara mutable. - Baris 9 (
*count += 1;): keduanya berakhir di baris yang sama: tambah satu. Tidak adaif, tidak adaelse, tidak ada pencarian ganda — tiga key berbeda diperlakukan dengan satu baris logika yang sama.
Coba bayangkan versi manual dari loop ini: contains_key atau get_mut di setiap putaran, cabang else untuk memasukkan counter pertama, dan seluruh ritual itu diulang enam kali. Pola entry(...).or_insert(0) yang diikuti *count += 1 adalah idiom standar Rust untuk penghitungan frekuensi — menghitung kata dalam dokumen, popularitas produk, atau jumlah kunjungan pengguna memakai bentuk yang persis sama.
Ringkasan #
Entry API menyatukan pengecekan keberadaan key dan pembaruan value menjadi satu operasi: entry(key) mengembalikan enum Entry dengan varian Occupied atau Vacant, dan or_insert(nilai_default) menutup kedua varian dengan satu reference &mut V — memakai value lama yang ada, atau menanam nilai default lebih dulu. Idiom entry(...).or_insert(0) diikuti dereference menjadi pola baku untuk menambah stok dan menghitung frekuensi. Chapter berikutnya melangkah ke penelusuran isi HashMap: cara iterasi yang jujur terhadap urutan acak, pembaruan value dalam loop, dan penghapusan entry.