Topik ini membahas Vec<T>, yaitu koleksi Rust yang menyimpan sejumlah nilai bertipe sama di dalam satu penyangga heap dengan jumlah yang dapat berubah selama program berjalan. Pembahasan mencakup cara membuat dan menumbuhkan vektor, perilaku kapasitas beserta pertumbuhan amortisasi, akses elemen lewat pengindeksan maupun metode get, serta pengeluaran elemen dengan drain dan retain. Setelah membaca topik ini, array dapat digantikan dengan vektor ketika jumlah elemen baru diketahui saat program berjalan, dan pilihan antara akses yang panik serta akses yang aman dapat diambil secara sadar.
Membuat dan menumbuhkan Vec#
Sebuah Vec<T> dibuat melalui dua pintu utama. Fungsi terasosiasi Vec::new() menghasilkan vektor kosong, dan karena tidak ada nilai yang menjadi petunjuk, tipe elemennya wajib dianotasikan secara eksplisit. Makro vec! membuat vektor yang sudah berisi nilai awal, sehingga tipenya disimpulkan dari daftar nilai yang dituliskan.
Elemen baru ditambahkan di ujung belakang dengan push, dan elemen terakhir dikeluarkan dengan pop. Karena pop pada vektor kosong tidak memiliki elemen untuk dikembalikan, hasilnya bertipe Option<T> alih-alih T secara langsung. Program berikut memakai kedua pintu pembuatan tersebut.
fn main() {
let awal = vec![10, 20, 30];
println!("dari makro : {:?}", awal);
let mut angka: Vec<i32> = Vec::new();
angka.push(40);
angka.push(50);
println!("setelah push: {:?}", angka);
let terakhir = angka.pop();
println!("pop : {:?}", terakhir);
println!("panjang : {}", angka.len());
println!("kosong : {}", angka.is_empty());
}dari makro : [10, 20, 30]
setelah push: [40, 50]
pop : Some(50)
panjang : 1
kosong : falseVektor awal langsung berisi tiga elemen dari makro vec!, sedangkan vektor angka dibuat kosong lalu diisi melalui dua push. Kedua variabel berakhir bertipe Vec<i32>, sehingga makro sekadar meringkas penulisan dan bukan mekanisme terpisah. Pemanggilan pop pada angka mengembalikan Some(50) karena vektor masih berisi dua elemen, kemudian len() melaporkan satu elemen yang tersisa dan is_empty() bernilai false. Penambahan elemen mengubah isi vektor, sehingga variabel yang menerima push wajib dideklarasikan dengan mut. Kelalaian ini ditolak compiler dengan error E0596 yang menyebut variabel tersebut perlu bersifat mutable.
Kapasitas dan pertumbuhan amortisasi#
Vektor menyimpan seluruh elemennya berdekatan dalam satu penyangga di heap. Selain panjang (len), vektor mencatat kapasitas (cap), yaitu banyaknya tempat yang sudah disiapkan penyangganya. Ketika len menyamai cap, push berikutnya mengalokasikan penyangga baru yang lebih besar, memindahkan seluruh elemen lama ke sana, lalu membebaskan penyangga lama. Kapasitas baru dipilih kira-kira dua kali lipat kapasitas sebelumnya pada implementasi saat ini, sehingga realokasi yang mahal terjadi semakin jarang seiring vektor membesar. Rata-rata biaya satu push tetap konstan, dan sifat inilah yang disebut pertumbuhan amortisasi.
Diagram berikut memperlihatkan struktur kendali vektor di stack yang menunjuk penyangga delapan tempat di heap. Tiga tempat pertama terisi elemen, sedangkan lima tempat sisanya merupakan cadangan yang belum terpakai.
Metode capacity() melaporkan kapasitas saat ini, dan with_capacity(n) menyiapkan penyangga dengan sekurang-kurangnya n tempat sejak awal. Program berikut mengamati keduanya sepanjang rangkaian push.
fn main() {
let mut v: Vec<i32> = Vec::new();
println!("awal : len={} cap={}", v.len(), v.capacity());
for i in 1..=5 {
v.push(i * 10);
println!("setelah push {} : len={} cap={}", i, v.len(), v.capacity());
}
let mut siap: Vec<i32> = Vec::with_capacity(8);
println!("with_capacity(8): len={} cap={}", siap.len(), siap.capacity());
for i in 1..=8 {
siap.push(i);
}
println!("terisi penuh : len={} cap={}", siap.len(), siap.capacity());
siap.push(9);
println!("push ke-9 : len={} cap={}", siap.len(), siap.capacity());
}awal : len=0 cap=0
setelah push 1 : len=1 cap=4
setelah push 2 : len=2 cap=4
setelah push 3 : len=3 cap=4
setelah push 4 : len=4 cap=4
setelah push 5 : len=5 cap=8
with_capacity(8): len=0 cap=8
terisi penuh : len=8 cap=8
push ke-9 : len=9 cap=16Keluaran tersebut memperlihatkan kapasitas yang melompat dari 0 ke 4 pada push pertama, bertahan sampai push keempat, kemudian naik menjadi 8 ketika push kelima tidak lagi muat. Vektor kedua dibuat dengan with_capacity(8), sehingga delapan push pertamanya berjalan tanpa realokasi dan kapasitasnya baru bertambah ketika push kesembilan tiba. Angka pasti pada keluaran tersebut merupakan detail implementasi dan dapat berbeda pada versi pustaka standar lain. Yang dijamin dokumentasi resmi adalah kapasitas tidak pernah lebih kecil daripada panjang, sehingga hubungan len() <= capacity() selalu berlaku.
Akses elemen dengan indeks dan get#
Elemen vektor diakses melalui dua cara yang berbeda perilakunya pada indeks di luar batas. Pengindeksan dengan tanda kurung siku mengembalikan elemen secara langsung, tetapi menghentikan program dengan panic apabila indeks berada di luar rentang yang sah. Metode get menerima indeks dan mengembalikan Option<&T>: varian Some membawa referensi ke elemen pada indeks tersebut, sedangkan None menyatakan bahwa indeks itu tidak ada. Karena hasilnya berupa Option, ketidakhadiran elemen ditangani dengan match atau if let alih-alih menggugurkan program.
fn main() {
let angka = vec![10, 20, 30];
println!("angka[1] : {}", angka[1]);
match angka.get(2) {
Some(nilai) => println!("get(2) : {nilai}"),
None => println!("get(2) : tidak ada"),
}
if let Some(nilai) = angka.get(100) {
println!("get(100) : {nilai}");
} else {
println!("get(100) : None");
}
}angka[1] : 20
get(2) : 30
get(100) : NonePengaksesan angka[1] berhasil karena indeks satu berada di dalam batas. Pemanggilan get(2) menghasilkan Some(30) yang dibongkar oleh match, sedangkan get(100) pada vektor berisi tiga elemen menghasilkan None yang diterima cabang else tanpa menghentikan program. Aturan praktisnya mengikuti asal indeks: indeks yang dijamin sah oleh logika program cukup diakses dengan pengindeksan langsung, sedangkan indeks yang berasal dari luar program atau dari perhitungan yang bisa meleset sebaiknya diakses dengan get. Perilaku panic pada pengindeksan dapat diperiksa langsung dengan program kecil berikut.
export PATH="$HOME/.cargo/bin:$PATH"
# buat program yang mengakses indeks di luar batas lewat pengindeksan langsung
printf 'fn main() {\n let angka = vec![10, 20, 30];\n println!("{}", angka[5]);\n}\n' > salah.rs
# kompilasi lalu jalankan; nomor thread pada pesan bervariasi sehingga disaring
rustc --edition=2021 salah.rs -o salah && ./salah 2>&1 | sed -E 's/ \([0-9]+\)//' | sed -n '1,3p'thread 'main' panicked at salah.rs:3:25:
index out of bounds: the len is 3 but the index is 5Pesan panic menyebut indeks yang diminta beserta panjang riil vektor, sehingga penyebabnya terbaca tanpa perlu menebak. Nomor thread tercetak dalam tanda kurung setelah kata main pada pesan asli, dan nomor itu berbeda pada setiap jalankan sehingga disaring dari keluaran contoh. Program yang panik berakhir dengan kode keluar 101. Panic menghentikan seluruh program, bukan hanya fungsi yang sedang berjalan, sehingga pengindeksan langsung pada data yang batasnya tidak pasti merupakan risiko yang tidak perlu.
Mengeluarkan elemen dengan drain dan retain#
Dua metode berikut mengeluarkan elemen tanpa perulangan indeks manual. Metode drain(rentang) menghapus seluruh elemen pada rentang tertentu sekaligus mengembalikan iterator yang memindahkan kepemilikan elemen tersebut keluar dari vektor, sehingga elemen yang dikeluarkan dapat dikumpulkan menjadi koleksi baru. Metode retain(predikat) menyaring di tempat: elemen yang membuat predikatnya mengembalikan false dibuang, sedangkan sisanya tetap berada di vektor dengan urutan relatif yang dijaga. Keduanya mengubah isi vektor, sehingga pemiliknya juga perlu mut.
fn main() {
let mut angka = vec![10, 20, 30, 40, 50];
let diambil: Vec<i32> = angka.drain(1..3).collect();
println!("diambil : {:?}", diambil);
println!("tersisa : {:?}", angka);
angka.retain(|&n| n > 20);
println!("setelah retain : {:?}", angka);
}diambil : [20, 30]
tersisa : [10, 40, 50]
setelah retain : [40, 50]Rentang 1..3 pada drain mengeluarkan elemen bernilai 20 dan 30, kemudian elemen setelah rentang bergeser ke depan sehingga vektor menyisakan [10, 40, 50]. Iterator hasil drain dikumpulkan menjadi Vec<i32> baru dengan collect, dan elemen berpindah dari vektor lama ke vektor baru tanpa penyalinan manual. Predikat |&n| n > 20 pada retain kemudian membuang 10 karena tidak memenuhi syarat, dan hasil akhirnya [40, 50]. Rentang pada drain memakai sintaks yang sama dengan irisan, yaitu batas awal inklusif dan batas akhir eksklusif, dan rentang yang melampaui panjang vektor ditolak saat program berjalan.
Kesalahan umum#
Menganggap len() sebagai indeks terakhir. Indeks elemen terakhir adalah len() - 1 karena pengindeksan dimulai dari nol. Pengaksesan angka[angka.len()] dengan pengindeksan langsung selalu panic karena indeks itu berada tepat satu langkah di luar batas, dan kesalahan semacam ini sering lolos dari pengujian ketika panjang vektor kebetulan kecil. Gunakan angka.len() - 1 untuk elemen terakhir, atau keluarkan dari belakang dengan pop yang sudah menangani keadaan kosong secara aman.
Menggeser elemen secara manual dengan perulangan indeks. Kebiasaan menghapus elemen satu per satu sambil mengiterasi indeks mudah salah karena setiap penghapusan menggeser posisi elemen setelahnya. drain menyelesaikan pengeluaran satu rentang dalam satu langkah, dan retain menyelesaikan penyaringan dalam satu langkah, keduanya tanpa risiko indeks melenceng. Tulis perulangan indeks manual hanya ketika urutan kerja benar-benar bergantung pada posisi elemen.
Ringkasan#
Vec<T>menyimpan nilai bertipe sama pada penyangga heap dengan jumlah yang dapat berubah, dibuat lewatVec::new()dengan anotasi tipe atau makrovec!dengan nilai awal.lenmelaporkan jumlah elemen,capacity()melaporkan banyaknya tempat penyangga, danpushmenumbuhkan kapasitas secara amortisasi, sedangkanwith_capacitymenyiapkan tempat di muka.- Pengindeksan langsung panik pada indeks di luar batas, sedangkan
getmengembalikanOption<&T>yang ditangani denganmatchatauif let. drainmengeluarkan rentang elemen sebagai iterator yang memindahkan kepemilikannya, danretainmenyaring elemen di tempat dengan urutan relatif yang dijaga.
Referensi#
- Storing Lists of Values with Vectors — The Rust Programming Language — Bab resmi tentang pembuatan vektor, pengindeksan, dan pola pemakaiannya.
- Struct Vec — Rust standard library
— Dokumentasi metode
Vec, termasukdrain,retain, danwith_capacity. - std::collections — Rust standard library — Ikhtisar koleksi bawaan beserta perbandingan kinerjanya.