Daftar di dunia nyata tidak pernah diam. Stok toko bertambah setengah jam sebelum tutup, pesanan dibatalkan di tengah proses, dan barang kadaluarsa dikeluarkan dari rak. Chapter sebelumnya kita sudah bisa membuat vector dan mengisinya lewat push; sekarang kita melengkapi kemampuan itu dengan operasi perubahan yang tersedia di Vec<T>: menyisipkan elemen di posisi tertentu dengan insert, menghapus elemen dengan remove, dan mengambil elemen terakhir dengan pop. Sepanjang jalan kita juga akan menyaksikan satu benturan klasik antara push dan aturan borrowing, lalu membuka kap mesin vector untuk melihat bagaimana kapasitas dan realokasi bekerja.
Menambah, Menyisipkan, dan Menghapus Elemen #
Kita pakai domain daftar restok toko kelontong. Program berikut memulai dari dua produk, menambah dua lagi dengan cara berbeda, lalu melakukan dua penghapusan dengan dua method berbeda:
fn main() {
let mut restock_list = vec!["Gula", "Teh"];
restock_list.push("Kopi");
restock_list.insert(0, "Beras");
println!("Daftar restok: {:?}", restock_list);
println!("Panjang daftar: {}", restock_list.len());
let removed = restock_list.remove(1);
println!("Produk dihapus dari posisi 1: {}", removed);
let last_item = restock_list.pop();
println!("Item terakhir di-pop: {:?}", last_item);
println!("Daftar akhir: {:?}", restock_list);
}Output yang Dihasilkan:
Daftar restok: ["Beras", "Gula", "Teh", "Kopi"]
Panjang daftar: 4
Produk dihapus dari posisi 1: Gula
Item terakhir di-pop: Some("Kopi")
Daftar akhir: ["Beras", "Teh"]Mari kita bedah kode di atas baris per baris:
- Baris 2 (
let mut restock_list = vec!["Gula", "Teh"];): vector awal berisi dua elemen bertipe&str. Kata kuncimutwajib karena seluruh operasi di program ini mengubah isi vector. - Baris 4 (
restock_list.push("Kopi");):pushmenempelkan"Kopi"di ujung kanan vector, persis seperti chapter sebelumnya. Hasil sementara:["Gula", "Teh", "Kopi"]. - Baris 5 (
restock_list.insert(0, "Beras");):insertmenyisipkan"Beras"pada index0, sehingga semua elemen lama bergeser satu langkah ke kanan. Hasilnya"Beras"menempati posisi terdepan dan panjang daftar menjadi4. - Baris 9 (
let removed = restock_list.remove(1);):removemenghapus elemen pada index1dan mengembalikan nilai elemen tersebut secara utuh, bukan sekadar membuangnya. Elemen-elemen setelah posisi1bergeser satu langkah ke kiri, dan variabelremovedkini memiliki nilai"Gula". - Baris 12 (
let last_item = restock_list.pop();):popmengambil elemen paling akhir dan mengembalikannya dalamOption<&str>—Nonejika vector sudah kosong. Karena daftar masih berisi"Kopi"di ujung, hasilnyaSome("Kopi"). - Baris 13-14 (
println!("Item terakhir ...", ...)): penanda{:?}mencetak nilai dengan format Debug sehingga tampilanOptionterlihat apa adanya, dan daftar akhir menyisakan dua elemen.
Perbedaan bentuk nilai kembalian remove dan pop mencerminkan risiko masing-masing. Method remove menerima index yang kita pilih sendiri, sehingga index di luar batas dianggap bug pemrograman dan dijawab dengan panic — kita saksikan pesannya di bagian akhir chapter. Method pop selalu bekerja di ujung vector tanpa index bebas, sehingga satu-satunya kemungkinan gagal adalah vector yang sudah kosong — keadaan normal yang layak dijawab None.
Kapasitas dan Realokasi #
Dari luar, push tampak seperti menulis ke slot kosong yang sudah menunggu. Kenyataannya, heap tidak menyiapkan slot gratis secara otomatis. Setiap kali len hendak melewati capacity, Rust memesan blok memori baru yang lebih besar, menyalin seluruh elemen ke blok baru, dan membuang blok lama — proses ini disebut realokasi. Program berikut membedah proses tersebut lewat method capacity:
fn main() {
let mut numbers = Vec::new();
println!("Baru dibuat : panjang {}, kapasitas {}", numbers.len(), numbers.capacity());
for _ in 1..=10 {
numbers.push(7);
}
println!("Setelah 10 push: panjang {}, kapasitas {}", numbers.len(), numbers.capacity());
let mut waitlist: Vec<String> = Vec::with_capacity(200);
println!("Waitlist : panjang {}, kapasitas {}", waitlist.len(), waitlist.capacity());
}Output yang Dihasilkan:
Baru dibuat : panjang 0, kapasitas 0
Setelah 10 push: panjang 10, kapasitas 16
Waitlist : panjang 0, kapasitas 200Mari kita bedah makna setiap baris kode:
- Baris 2 (
let mut numbers = Vec::new();): vector baru belum memesan memori heap sama sekali — panjang dan kapasitas sama-sama0. - Baris 5-7 (
for _ in 1..=10 { numbers.push(7); }): sepuluh kalipushmemicu realokasi bertahap. Strategi standar Rust melipatgandakan kapasitas:4, lalu8, lalu16. Setelah push kesepuluh, panjang tepat10dan kapasitas16— masih tersisa 6 slot cadangan. - Baris 10 (
let mut waitlist: Vec<String> = Vec::with_capacity(200);):with_capacitymemesan 200 slot di muka. Anotasi tipeVec<String>diperlukan karena vector ini belum berisi elemen apa pun sehingga compiler tidak punya petunjuk tipe.
Melipatgandakan kapasitas membuat biaya realokasi terbagi rata: jumlah operasi penyalinan total tetap sebanding dengan jumlah push, sehingga rangkaian banyak push tetap berjalan efisien secara keseluruhan. Meski begitu, kalau kita tahu pasti jumlah elemen yang akan masuk — misalnya 200 peserta daftar tunggu — Vec::with_capacity(200) menghemat seluruh proses realokasi dengan memesan sekali saja.
Pinjaman dan push Tidak Bisa Berjalan Bersamaan #
Sekarang benturan klasiknya. Kode berikut membaca elemen pertama vector, lalu melakukan push, lalu mencetak hasil pembacaan tersebut:
fn main() {
let mut teams = vec![String::from("Garuda"), String::from("Rajawali")];
let first_team = &teams[0];
teams.push(String::from("Elang"));
println!("Tim pertama: {}", first_team);
}Pesan error kompilator:
error[E0502]: cannot borrow `teams` as mutable because it is also borrowed as immutable
--> main.rs:5:5
|
4 | let first_team = &teams[0];
| ----- immutable borrow occurs here
5 | teams.push(String::from("Elang"));
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ mutable borrow occurs here
6 |
7 | println!("Tim pertama: {}", first_team);
| ---------- immutable borrow later used here
error: aborting due to 1 previous error
For more information about this error, try `rustc --explain E0502`.Pesan E0502 menyatakan dua pinjaman yang tidak boleh hidup bersama: first_team meminjam teams secara immutable, sementara push membutuhkan pinjaman mutable. Penolakan ini bukan kekakuan aturan borrowing, melainkan perlindungan nyata: push dapat memicu realokasi, dan saat realokasi terjadi, semua reference lama menunjuk ke blok heap yang sudah dibuang. Reference yang menggantung (dangling reference) itulah yang dicegah compiler sebelum program sempat berjalan. Dua perbaikan yang tersedia: pindahkan println! sebelum push, atau ambil salinan nilainya dengan clone sehingga tidak ada pinjaman yang bertahan.
Remove dengan Index Ilegal #
insert dan remove sama-sama menuntut index valid. Ketika index berada di luar panjang vector, program tidak mengembalikan Option — program panic, sama seperti operator index []:
fn main() {
let mut restock_list = vec!["Beras", "Teh"];
restock_list.remove(5);
}Output yang Dihasilkan:
thread 'main' (17018) panicked at main.rs:3:18:
removal index (is 5) should be < len (is 2)
note: run with `RUST_BACKTRACE=1` environment variable to display a backtracePesan panic membandingkan dua angka secara gamblang: index yang diminta adalah 5, sedangkan panjang vector hanya 2. Sama seperti panic index di chapter sebelumnya, program keluar dengan exit code 101 dan angka ID thread akan berbeda di setiap eksekusi. Ketika posisi penghapusan datang dari sumber yang tidak pasti, kombinasi len() dan get — atau penghitungan mundur dengan pop — adalah pilihan yang lebih aman daripada remove mentah-mentah.
Ringkasan #
Empat operasi inti perubahan vector kini kita kuasai: push menambah di ujung dalam waktu hampir konstan, insert menyisipkan di posisi mana pun dengan menggeser elemen di belakangnya, remove menghapus pada index tertentu dan mengembalikan nilainya, dan pop mengambil elemen terakhir dalam Option. Di balik layar, push tumbuh dengan melipatgandakan kapasitas dan melakukan realokasi — itulah akar penolakan E0502 ketika push berbenturan dengan pinjaman yang masih hidup. Chapter berikutnya melanjutkan ke pola yang paling sering dipakai bersama vector: menelusuri seluruh elemennya dengan for.