Ketika sebuah Vec<String> berisi tiga judul buku, pertanyaan yang jarang ditanyakan tetapi sangat penting adalah: siapa pemilik ketiga String tersebut? Aturan ownership yang kita pelajari di section sebelumnya tidak berhenti di pintu koleksi — justru di sinilah aturan tersebut bekerja paling sering. Elemen yang di-push ke dalam vector berpindah kepemilikan ke vector, vector yang berpindah tangan membawa seluruh elemennya, dan elemen yang dikeluarkan membawa pulang kepemilikannya sendiri. Chapter ini membedah ketiga peristiwa itu memakai domain rak buku perpustakaan, sekaligus melatih mata kita membaca pesan E0382 yang muncul ketika alur kepemilikan dilanggar.

Vec Menjadi Pemilik Elemennya #

Relasi antara vector dan elemennya adalah relasi kepemilikan bertingkat: vector memiliki blok memori di heap, dan vector juga memiliki setiap nilai yang duduk di blok tersebut. Konsekuensinya terasa saat akhir hidup — ketika variable vector keluar dari scope, Rust menjalankan proses drop atas vector, dan proses drop vector otomatis menjalankan drop atas seluruh elemen di dalamnya. Kita tidak perlu menulis satu pun pemanggilan pembersihan; tidak ada kebocoran memori dari daftar yang terlupakan.

Mekanisme yang sama juga menjelaskan mengapa compiler bisa menjamin masa hidup elemen: reference &library[0] hanya sah selama library masih hidup, karena elemen bernapas bersama pemiliknya. Seluruh adegan kepemilikan yang sudah kita kenal — move, borrow, clone — kini berlaku satu tingkat lebih dalam.

Mari kita perketat intuisi dengan satu eksperimen pikiran. Jika library diisi tiga buku lalu keluar dari scope di akhir blok, urutan kejadiannya berjalan seperti ini: Rust menjalankan drop atas library, drop vector membawa serta drop ketiga String di dalamnya, dan barulah blok memori di heap dikembalikan ke sistem. Tidak ada ketiga String yang lolos hidup karena sudah “diserahkan” ke vector — justru penyerahan itulah yang menjamin kematian yang tertib. Perbandingannya dengan bahasa ber-garbage collector cukup mencolok: di sana koleksi dan isinya dibersihkan kapan pun collector memutuskan; di Rust, momen kematian diketahui pasti dan bisa diandalkan untuk melepaskan resource lain tepat waktu.

push Memindahkan Nilai ke Dalam Vector #

Kita mulai dari peristiwa paling mendasar: memasukkan buku ke rak. Method push bukan sekadar menempel nilai — push menerima kepemilikan penuh atas nilai yang dikirimkan:

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

    let book_title = String::from("Laskar Pelangi");
    library.push(book_title);

    println!("Buku pertama di rak: {}", library[0]);
    println!("Jumlah buku: {}", library.len());
}

Output yang Dihasilkan:

text
Buku pertama di rak: Laskar Pelangi
Jumlah buku: 1

Mari kita bedah kode di atas baris per baris:

  1. Baris 2 (let mut library = Vec::new();): library lahir sebagai vector kosong bertipe Vec<String> — tipe elemennya akan disimpulkan compiler dari elemen pertama yang masuk.
  2. Baris 4 (let book_title = String::from(...)): book_title menjadi pemilik String “Laskar Pelangi” beserta data teksnya di heap.
  3. Baris 5 (library.push(book_title);): kepemilikan String berpindah dari book_title ke library. Sejak baris ini, library-lah pemilik sah buku tersebut — variabel book_title sudah tidak memiliki apa pun.
  4. Baris 7-8 (library[0] dan library.len()): buku dibaca lewat pemilik barunya, dan panjang vector naik menjadi 1 karena satu elemen kini dimiliki library.

Sekarang kita langgar aturannya dengan sengaja — mencoba memakai book_title setelah buku dipindahkan ke rak:

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

    let book_title = String::from("Laskar Pelangi");
    library.push(book_title);

    println!("Judul asli: {}", book_title);
}

Pesan error kompilator:

text
error[E0382]: borrow of moved value: `book_title`
 --> main.rs:7:32
  |
4 |     let book_title = String::from("Laskar Pelangi");
  |         ---------- move occurs because `book_title` has type `String`, which does not implement the `Copy` trait
5 |     library.push(book_title);
  |                  ---------- value moved here
6 |
7 |     println!("Judul asli: {}", book_title);
  |                                ^^^^^^^^^^ value borrowed here after move
  |
help: consider cloning the value if the performance cost is acceptable
  |
5 |     library.push(book_title.clone());
  |                            ++++++++

error: aborting due to 1 previous error

For more information about this error, try `rustc --explain E0382`.

Pesan E0382 menunjuk dua lokasi sekaligus: kepemilikan berpindah di baris 5 saat push, lalu dipinjam di baris 7 saat println! — dan meminjam nilai tanpa pemilik adalah hal mustahil yang ditolak compiler. Perhatikan saran perbaikannya: compiler menawarkan clone ketika kedua salinan benar-benar dibutuhkan.

Membuat Salinan dengan clone #

Jika judul buku memang harus tersedia dua-duanya — satu di rak, satu di catatan pustakawan — method clone membuat salinan mandiri:

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

    let book_title = String::from("Laskar Pelangi");
    library.push(book_title.clone());

    println!("Judul asli masih bisa dipakai: {}", book_title);
    println!("Rak menyimpan salinannya: {}", library[0]);
}

Output yang Dihasilkan:

text
Judul asli masih bisa dipakai: Laskar Pelangi
Rak menyimpan salinannya: Laskar Pelangi

Setelah clone, dua String berdiri sendiri-sendiri dengan blok heap masing-masing: book_title tetap milik variabel, salinannya milik library. Kedua nilai hidup dan mati terpisah. Harga yang dibayar adalah alokasi heap dan penyalinan byte teks — murah untuk satu judul, tetapi patut dipikirkan ulang ketika data berukuran besar atau berjumlah ribuan. Kebiasaan baik di Rust: pilih clone karena kedua salinan memang dibutuhkan, bukan karena sekadar menghapus error tanpa memahami alur kepemilikannya.

Memindahkan Seluruh Vector #

Kepemilikan bertingkat bekerja dua arah: elemen berpindah ke dalam vector, dan vector berpindah beserta seluruh elemennya. Fungsi penghitung buku berikut menerima Vec<String> sebagai parameter — artinya fungsi meminta kepemilikan atas seluruh rak:

rust
fn count_books(library: Vec<String>) -> usize {
    library.len()
}

fn main() {
    let mut library = vec![
        String::from("Laskar Pelangi"),
        String::from("Bumi Manusia"),
    ];
    library.push(String::from("Negeri 5 Menara"));

    let total_books = count_books(library);
    println!("Total buku di perpustakaan: {}", total_books);
}

Output yang Dihasilkan:

text
Total buku di perpustakaan: 3

Mari kita telaah alurnya: Baris 1 mendeklarasikan parameter library bertipe Vec<String> tanpa &, sehingga pemanggilan count_books(library) di baris 12 memindahkan vector beserta ketiga buku di dalamnya ke dalam fungsi. Ketika fungsi selesai, parameter library keluar scope dan seluruh koleksi — tiga String sekaligus — di-drop. Memakai variabel library di main setelah pemanggilan itu akan menghasilkan E0382 yang persis sekeluarga dengan error book_title di atas. Jika fungsi hanya perlu membaca rak, parameter &Vec<String> (atau cukup reference ke isinya) menghindari pemindahan total — teknik yang sudah kita kuasai di section Ownership dan akan kembali muncul di section Slice.

Mengeluarkan Elemen dengan pop dan remove #

Alur kepemilikan juga bisa berjalan keluar dari vector: pop dan remove menyerahkan elemen kepada pemanggil, bukan membuangnya:

rust
fn main() {
    let mut library = vec![
        String::from("Laskar Pelangi"),
        String::from("Bumi Manusia"),
    ];

    if let Some(book) = library.pop() {
        println!("Buku '{}' dipinjam pembaca", book);
    }

    let moved_book = library.remove(0);
    println!("Buku '{}' dipindah ke rak baru", moved_book);

    println!("Rak lama sekarang kosong: {}", library.len());
}

Output yang Dihasilkan:

text
Buku 'Bumi Manusia' dipinjam pembaca
Buku 'Laskar Pelangi' dipindah ke rak baru
Rak lama sekarang kosong: 0

Mari kita bedah perpindahan kepemilikannya:

  1. Baris 7 (if let Some(book) = library.pop()): pop mengeluarkan "Bumi Manusia" dari ujung vector dan memindahkan kepemilikan String tersebut ke pattern variable book di dalam cabang Some. Vector kehilangan satu elemen; variable book menjadi pemilik sah.
  2. Baris 11 (let moved_book = library.remove(0);): remove mengambil elemen index 0 dengan mekanisme yang sama — "Laskar Pelangi" kini dimiliki moved_book, bukan lagi library.
  3. Baris 14 (library.len()): rak lama melaporkan isi kosong. Kedua String tidak di-drop bersama vector karena keduanya sudah berpindah ke variabel lain sebelum vector mati.

Ringkasan #

Vector bukan wadah pasif melainkan pemilik: push memindahkan kepemilikan elemen ke vector — memakai variabel lama setelahnya memicu E0382 — dan clone adalah pintu keluar ketika dua salinan benar-benar dibutuhkan. Vector yang berpindah ke fungsi membawa seluruh elemennya dan menghabiskannya di ujung scope fungsi, sementara pop dan remove mengembalikan kepemilikan elemen ke pemanggil sebelum vector ditutup. Dengan kepemilikan yang mapan, kita siap meninggalkan dunia daftar berindex dan masuk ke koleksi yang menyandingkan key dengan value: HashMap.