Program sungguhan jarang bekerja dengan satu nilai saja. Kasir toko mencatat puluhan item belanja, pelatih mencetak skor setiap babak, dan guru merekap nilai banyak siswa. Membuat variabel terpisah seperti item1, item2, hingga item100 jelas bukan solusi, terutama karena jumlah data sering baru diketahui saat program berjalan. Rust menjawab kebutuhan tersebut dengan Vec<T> — koleksi yang tumbuh secara dinamis dan menyimpan deretan nilai bertipe sama di heap. Chapter ini membuka section Koleksi dengan membentuk vector pertama kita, kemudian membaca isinya lewat dua pintu: operator index [] dan method get.

Masalah yang Dipecahkan Vec #

Array [T; N] yang sudah kita kenal di section Dasar Sintaks menyimpan jumlah elemen tetap N yang harus diketahui saat kompilasi. Sifat tersebut cocok untuk data yang ukurannya pasti sejak awal, tetapi tidak cocok untuk data yang bertambah saat runtime seperti daftar pesanan pelanggan yang terus masuk. Compiler menolak penambahan elemen pada array biasa karena ukuran array melekat pada tipenya.

Vector memutus keterikatan itu. Sebuah Vec<T> menyimpan elemen bertipe T sebanyak apapun di memori heap, menambah elemen lewat push, dan menghapus elemen lewat operasi yang akan kita bahas di chapter berikutnya. Notasi Vec<T> dibaca “vector of T”, misalnya Vec<i32> berarti vector berisi angka i32 dan Vec<String> berarti vector berisi String. Seluruh elemen di dalam satu vector harus bertipe sama — daftar belanja berisi nama produk semua, bukan nama produk bercampur angka harga.

Membuat Vector dengan Macro vec #

Cara tercepat membuat vector berisi data awal adalah macro vec!. Kita pakai domain daftar belanja:

rust
fn main() {
    let shopping_list = vec!["Beras", "Gula", "Minyak Goreng"];

    println!("Jumlah item: {}", shopping_list.len());
    println!("Item pertama: {}", shopping_list[0]);
    println!("Item terakhir: {}", shopping_list[shopping_list.len() - 1]);
}

Output yang Dihasilkan:

text
Jumlah item: 3
Item pertama: Beras
Item terakhir: Minyak Goreng

Mari kita bedah baris-baris kode di atas:

  1. Baris 2 (let shopping_list = vec![...]): macro vec! membuat Vec<&str> berisi tiga string literal dan mengikat vector tersebut ke variabel shopping_list. Dibanding menulis Vec::new() lalu tiga kali push, macro vec! meringkas semuanya menjadi satu baris.
  2. Baris 4 (shopping_list.len()): method len mengembalikan jumlah elemen saat ini, yaitu 3.
  3. Baris 5 (shopping_list[0]): operator index [] membaca elemen pada posisi ke-0. Index vector dimulai dari nol, sama seperti array, sehingga shopping_list[0] menghasilkan "Beras".
  4. Baris 6 (shopping_list[shopping_list.len() - 1]): ekspresi di dalam [...] boleh berupa perhitungan apa pun asalkan bertipe usize. Karena len() bernilai 3, index yang dievaluasi adalah 2, elemen terakhir vector.

Membuat Vector Kosong dan Menambah Elemen #

Saat jumlah data baru muncul dari input pengguna atau pembacaan file, kita memulai dari vector kosong. Pembuatannya memakai Vec::new(), lalu elemen masuk satu per satu lewat push:

rust
fn main() {
    let mut shopping_list = Vec::new();

    shopping_list.push("Beras");
    shopping_list.push("Gula");
    shopping_list.push("Minyak Goreng");

    println!("Jumlah item: {}", shopping_list.len());

    let index = 1;
    println!("Item pada indeks {}: {}", index, shopping_list[index]);
}

Output yang Dihasilkan:

text
Jumlah item: 3
Item pada indeks 1: Gula

Mari kita telaah kode di atas baris per baris:

  1. Baris 2 (let mut shopping_list = Vec::new();): Vec::new() membuat vector dengan nol elemen. Kata kunci mut wajib karena push membutuhkan akses &mut untuk mengubah isi vector.
  2. Baris 4-6 (shopping_list.push(...)): setiap push menempelkan satu elemen di ujung kanan vector. Tipe elemen pertama yang di-push, yaitu &str, menentukan tipe seluruh vector menjadi Vec<&str> — compiler menganalisis seluruh fungsi sebelum menetapkan tipe shopping_list.
  3. Baris 8 (shopping_list.len()): panjang vector tumbuh dari 0 menjadi 3 setelah tiga kali push.
  4. Baris 10-11 (let index = 1; ...): index tidak harus literal. Variabel index bertipe usize — tipe integer yang diterima operator [...] — dan dipakai untuk membaca elemen "Gula".

Mekanisme Vec di Balik Layar #

Nilai shopping_list di stack sebenarnya hanya sebuah struct kecil berisi tiga field: pointer ke blok data di heap, len atau jumlah elemen terpakai, dan capacity atau jumlah slot yang benar-benar dialokasikan. Elemen-elemen sendiri berbaris berdampingan (contiguous) di heap, sehingga lompatan ke index tertentu hanya berupa perkalian dan penjumlahan alamat — itulah sebabnya membaca shopping_list[i] selesai dalam waktu konstan.

Diagram di atas memperlihatkan pembagian kerjanya: field pointer, len, dan capacity hidup di stack, sedangkan data elemen hidup di heap. Kapasitas boleh lebih besar daripada panjang karena push sengaja memesan slot ekstra; saat slot penuh, Rust mengalokasikan blok heap yang lebih besar, memindahkan seluruh elemen, lalu memperbarui pointer. Detail proses realokasi ini kita bedah tuntas di chapter berikutnya.

Batas Index: Panic Out of Bounds #

Operator index [] bekerja dengan satu asumsi: index yang kita berikan valid. Compiler Rust tidak dapat membuktikan kebenaran index pada runtime, jadi setiap akses [...] dijaga pemeriksaan batas (bounds check). Ketika index melewati panjang vector, program berhenti dengan panic:

rust
fn main() {
    let scores = vec![2, 0, 1];
    println!("Skor babak keenam: {}", scores[5]);
}

Output yang Dihasilkan:

text
thread 'main' (17016) panicked at main.rs:3:45:
index out of bounds: the len is 3 but the index is 5
note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace

Pesan panic tersebut membaca fakta dengan lugas: panjang vector hanya 3 (index sah 0 sampai 2), sedangkan kode meminta index 5. Angka 3:45 menunjuk lokasi baris dan kolom akses yang bermasalah, dan program keluar dengan exit code 101 sebagai tanda kegagalan. Angka dalam tanda kurung setelah main adalah ID thread yang nilainya berbeda-beda pada setiap eksekusi, jadi angka di komputer Anda kemungkinan tidak sama.

Alternatif Aman: Method get #

Method get mengambil index bertipe usize dan mengembalikan Option<&T>: Some berisi reference ke elemen ketika index valid, None ketika index melewati batas. Kita sudah mengenal enum Option di section Struktur Data, dan inilah momen kemunculannya yang paling sering di Rust:

rust
fn main() {
    let scores = vec![2, 0, 1];

    match scores.get(5) {
        Some(score) => println!("Skor babak: {}", score),
        None => println!("Indeks 5 tidak tersedia, hanya ada {} babak", scores.len()),
    }

    println!("Program tetap berjalan sampai selesai");
}

Output yang Dihasilkan:

text
Indeks 5 tidak tersedia, hanya ada 3 babak
Program tetap berjalan sampai selesai

Perhatikan dua fakta penting pada hasil eksekusi di atas: kasus index ilegal ditangani sebagai cabang None biasa, dan baris terakhir masih tercetak karena program tidak pernah panic. Mari kita bedah strukturnya:

  1. Baris 4 (match scores.get(5)): get(5) mengembalikan Option<&i32>. Karena panjang vector hanya 3, hasilnya pasti None; tidak ada elemen yang disentuh sama sekali.
  2. Baris 5 (Some(score) => ...): cabang ini berjalan ketika index valid. Pattern Some(score) mengikat reference ke elemen ke variabel score, siap dicetak.
  3. Baris 6 (None => ...): cabang penanganan index di luar batas. Di sinilah kita menulis keputusan bisnis yang masuk akal — menampilkan pesan, memakai nilai cadangan, atau melompat ke proses berikutnya.
  4. Baris 9 (println!("Program tetap berjalan...")): eksekusi berlanjut normal setelah match selesai. Perbandingannya dengan versi panic di atas sangat kontras: bug yang sama, dua nasib program yang berbeda.

Ringkasan #

Vector menyimpan deretan nilai bertipe sama di heap dengan panjang yang bisa tumbuh: macro vec! untuk data awal, Vec::new() plus push untuk data yang terisi bertahap, dan tipe elemen ditentukan dari data pertama yang masuk. Pembacaan elemen memiliki dua pintu — operator index [] yang cepat tetapi panic dengan pesan index out of bounds saat index melewati batas, dan method get yang mengembalikan Option<&T> agar kasus index ilegal ditangani sebagai cabang None. Di chapter berikutnya kita lengkapi keterampilan dasar ini dengan operasi mengubah isi vector: menambah di tengah, menghapus, dan mengelola kapasitasnya.