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:
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:
Buku pertama di rak: Laskar Pelangi
Jumlah buku: 1Mari kita bedah kode di atas baris per baris:
- Baris 2 (
let mut library = Vec::new();):librarylahir sebagai vector kosong bertipeVec<String>— tipe elemennya akan disimpulkan compiler dari elemen pertama yang masuk. - Baris 4 (
let book_title = String::from(...)):book_titlemenjadi pemilikString“Laskar Pelangi” beserta data teksnya di heap. - Baris 5 (
library.push(book_title);): kepemilikanStringberpindah daribook_titlekelibrary. Sejak baris ini,library-lah pemilik sah buku tersebut — variabelbook_titlesudah tidak memiliki apa pun. - Baris 7-8 (
library[0]danlibrary.len()): buku dibaca lewat pemilik barunya, dan panjang vector naik menjadi1karena satu elemen kini dimilikilibrary.
Sekarang kita langgar aturannya dengan sengaja — mencoba memakai book_title setelah buku dipindahkan ke rak:
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:
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:
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:
Judul asli masih bisa dipakai: Laskar Pelangi
Rak menyimpan salinannya: Laskar PelangiSetelah 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:
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:
Total buku di perpustakaan: 3Mari 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:
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:
Buku 'Bumi Manusia' dipinjam pembaca
Buku 'Laskar Pelangi' dipindah ke rak baru
Rak lama sekarang kosong: 0Mari kita bedah perpindahan kepemilikannya:
- Baris 7 (
if let Some(book) = library.pop()):popmengeluarkan"Bumi Manusia"dari ujung vector dan memindahkan kepemilikanStringtersebut ke pattern variablebookdi dalam cabangSome. Vector kehilangan satu elemen; variablebookmenjadi pemilik sah. - Baris 11 (
let moved_book = library.remove(0);):removemengambil elemen index0dengan mekanisme yang sama —"Laskar Pelangi"kini dimilikimoved_book, bukan lagilibrary. - Baris 14 (
library.len()): rak lama melaporkan isi kosong. KeduaStringtidak 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.