Error Rust E0507: cannot move out of a value behind a shared reference
E0507 artinya kamu mencoba mengambil sebuah nilai (memindahkannya, move) keluar dari sesuatu yang hanya kamu pinjam. Nilai yang dipinjam boleh kamu lihat, tapi tidak boleh kamu bawa pergi, karena pemilik aslinya akan ditinggali “lubang”. Error ini sangat umum, terutama dengan Vec<String>, field struct, dan Option. Perbaikannya biasanya meminjam saja, atau memakai .clone(). Halaman ini menunjukkan perbaikannya dulu, lalu menjelaskannya. Untuk error lain di seri ini, lihat Penjelasan error compiler Rust.
Pesan error-nya
Section titled “Pesan error-nya”error[E0507]: cannot move out of `self.name` which is behind a shared reference --> src/main.rs:7:9 |7 | self.name // ❌ mencoba menyerahkan String yang hanya dipinjam oleh `self` | ^^^^^^^^^ move occurs because `self.name` has type `String`, which does not implement the `Copy` trait |help: consider cloning the value if the performance cost is acceptable |7 | self.name.clone() // ❌ mencoba menyerahkan String yang hanya dipinjam oleh `self` | ++++++++Dalam bahasa sederhana: “self.name tidak bisa di-move keluar karena berada di balik referensi bersama (shared reference)”. Compiler juga menyarankan .clone().
Ini program yang menghasilkan error tersebut:
struct User { name: String,}
impl User { fn name(&self) -> String { self.name // ❌ mencoba menyerahkan String yang hanya dipinjam oleh `self` }}
fn main() { let u = User { name: String::from("Ferris") }; println!("{}", u.name());}Kode error yang sama muncul dengan beberapa kalimat berbeda. Semuanya berarti “kamu hanya meminjam ini, jadi kamu tidak bisa memindahkannya”:
cannot move out of index of `Vec<String>`untuklet first = names[0];cannot move out of `*r` which is behind a shared referenceuntuklet owned = *r;denganr: &Stringcannot move out of `nick` as enum variant `Some` which is behind a shared referenceuntukmatch *nick { Some(n) => n, ... }cannot move out of `report`, a captured variable in an `Fn` closurecannot move out of `self.log` which is behind a mutable reference(ya, bahkan&mutpun tidak membolehkanmu mengambilnya begitu saja)cannot move out of an `Rc`untuklet s: String = *shared;
Apa artinya
Section titled “Apa artinya”Lihat baris 7. Method itu mengembalikan String, sebuah nilai yang dimiliki (owned). Jadi self.name akan memindahkan string itu keluar dari struct dan menyerahkannya ke pemanggil. Tapi method itu hanya punya &self, sebuah borrow bersama (baca-saja) atas user tersebut. Ia tidak memiliki user-nya, jadi ia tidak bisa menyerahkan nama user itu.
Bagian move occurs because ... does not implement the Copy trait juga penting. Kalau field-nya bertipe u32, Rust akan diam-diam menyalinnya dan tidak ada error. String memiliki memori di heap, jadi ia hanya bisa dipindahkan, tidak bisa disalin begitu saja.
Bayangkan kamu meminjam rak buku temanmu untuk satu sore. Kamu boleh membaca buku mana pun di rak itu. Tapi kamu tidak boleh membawa pulang bukunya: raknya bukan milikmu, dan temanmu akan pulang mendapati ada slot yang kosong.
Kenapa Rust melakukan ini
Section titled “Kenapa Rust melakukan ini”Setiap nilai punya tepat satu owner (pemilik), dan owner itulah yang membersihkannya saat ia hilang (lihat Ownership, kepemilikan). Kalau kamu bisa memindahkan self.name keluar dari User yang dipinjam, user itu akan tetap mengira ia memiliki string tersebut. Nanti saat user itu di-drop, ia akan membebaskan string itu untuk kedua kalinya, atau ada yang membaca field yang sudah tidak ada. Itulah “use after free” atau “double free”, dua bug terburuk di C dan C++.
Vec juga sama. names[0] tetap dimiliki oleh vektornya. Kalau let first = names[0]; memindahkannya keluar, vektor itu akan punya lubang di indeks 0, dan akan mencoba membebaskan slot itu lagi saat vektornya di-drop. Jadi indexing hanya memberimu akses, tidak pernah ownership.
Aturannya dalam satu kalimat: lewat sebuah referensi, kamu boleh membaca dan (dengan &mut) mengubah, tapi tidak boleh mengambil. Kalau kamu ingin ownership, kamu harus memiliki wadahnya, atau meninggalkan sesuatu di tempat nilai itu.
Cara memperbaikinya
Section titled “Cara memperbaikinya”Perbaikan 1: Pinjam, jangan pindahkan
Section titled “Perbaikan 1: Pinjam, jangan pindahkan”Pilih ini kalau kamu hanya perlu membaca nilainya. Hampir selalu inilah perbaikan yang tepat. Kembalikan &str alih-alih String, tulis &v[0] alih-alih v[0], dan pinjam isi sebuah Option dengan as_deref() atau as_ref():
struct User { name: String, nickname: Option<String>,}
impl User { fn name(&self) -> &str { &self.name }
fn display_name(&self) -> &str { match self.nickname.as_deref() { Some(nick) => nick, None => &self.name, } }}
fn describe(nick: &Option<String>) -> &str { match nick { Some(n) => n, None => "anonim", }}
fn main() { let users = vec![ User { name: String::from("Ferris"), nickname: Some(String::from("kepiting")) }, User { name: String::from("Corro"), nickname: None }, ]; let first = &users[0]; println!("Pengguna pertama: {}", first.name()); for user in &users { println!("Ditampilkan sebagai: {}", user.display_name()); } println!("Nama panggilan: {}", describe(&users[1].nickname));}Output:
Pengguna pertama: FerrisDitampilkan sebagai: kepitingDitampilkan sebagai: CorroNama panggilan: anonimLihat describe: ia melakukan match pada nick (referensinya), bukan *nick. Maka n adalah &String, sebuah borrow, dan tidak ada yang dipindahkan. Menulis match *nick { Some(n) => ... } berarti meminta String yang dimiliki di n, dan itulah E0507. Petunjuk dari compiler juga bilang begitu: consider removing the dereference here (coba hapus dereference di sini).
as_deref() mengubah &Option<String> menjadi Option<&str>; as_ref() memberi Option<&String>. Pakai keduanya sebelum .unwrap(), .map(), atau match, supaya kamu bekerja dengan borrow, bukan dengan nilai yang dimiliki. Hal yang sama berlaku untuk *r dengan r: &String: biasanya kamu sama sekali tidak butuh *, cukup pakai r. Lihat Option dan Borrowing (peminjaman).
Perbaikan 2: Clone nilainya
Section titled “Perbaikan 2: Clone nilainya”Pilih ini kalau kamu benar-benar butuh salinanmu sendiri, misalnya untuk disimpan di tempat lain sementara yang asli tetap di tempatnya. Compiler menyarankannya di hampir setiap pesan E0507:
struct User { name: String,}
impl User { fn name(&self) -> String { self.name.clone() }}
fn main() { let names = vec![String::from("Ferris"), String::from("Corro")]; let first = names[0].clone(); println!("{first} ada di urutan pertama, daftarnya masih berisi {} nama", names.len());
let u = User { name: String::from("Ferris") }; let mut badge = u.name(); badge.push_str(" (admin)"); println!("{badge} / {}", u.name);}Output:
Ferris ada di urutan pertama, daftarnya masih berisi 2 namaFerris (admin) / FerrisMeng-clone sebuah nama atau daftar pendek itu murah, dan sama sekali tidak apa-apa, apalagi selagi kamu masih belajar. Ini baru jadi masalah untuk data besar di dalam loop yang sangat sering berjalan. Kalau borrow (Perbaikan 1) sudah cukup, pilih itu.
Perbaikan 3: Ambil nilainya dan tinggalkan penggantinya
Section titled “Perbaikan 3: Ambil nilainya dan tinggalkan penggantinya”Pilih ini kalau kamu punya akses &mut dan ingin benar-benar memindahkan nilainya keluar, misalnya untuk menyerahkan log yang sudah selesai atau tugas berikutnya. Kamu tidak boleh meninggalkan lubang, tapi kamu boleh menaruh penggantinya:
std::mem::take(&mut x)mengambil nilainya dan meninggalkan nilai default-nya (""untukString, kosong untukVec).option.take()mengambil isi sebuahOptiondan meninggalkanNone.std::mem::replace(&mut x, new)menaruhnewdan memberikan nilai lamanya kepadamu.
use std::mem;
#[derive(Debug)]enum Status { Queued, Running,}
struct Job { log: String, next_step: Option<String>, status: Status,}
impl Job { fn take_log(&mut self) -> String { mem::take(&mut self.log) }
fn start_next(&mut self) -> Option<String> { self.next_step.take() }
fn set_status(&mut self, new: Status) -> Status { mem::replace(&mut self.status, new) }}
fn main() { let mut job = Job { log: String::from("build ok"), next_step: Some(String::from("deploy")), status: Status::Queued, }; let old = job.set_status(Status::Running); println!("Status: {old:?} -> {:?}", job.status); println!("Langkah berikutnya: {:?}", job.start_next()); println!("Langkah berikutnya lagi: {:?}", job.start_next()); println!("Log: {:?}", job.take_log()); println!("Log sekarang: {:?}", job.log);}Output:
Status: Queued -> RunningLangkah berikutnya: Some("deploy")Langkah berikutnya lagi: NoneLog: "build ok"Log sekarang: ""Struct-nya tidak pernah ditinggali lubang: setelah take_log, log-nya menjadi string kosong; setelah start_next, langkahnya menjadi None. Inilah perbaikan standar untuk “cannot move out of self.x which is behind a mutable reference”. mem::take membutuhkan tipe yang mengimplementasikan Default; untuk tipe lain, pakai mem::replace.
Perbaikan 4: Miliki koleksinya, lalu pindahkan isinya keluar
Section titled “Perbaikan 4: Miliki koleksinya, lalu pindahkan isinya keluar”Pilih ini kalau kamu sudah selesai dengan koleksi atau struct-nya dan menginginkan isinya. Kalau kamu yang memiliki Vec-nya, vektor itu bisa menyerahkan item-itemnya: remove(i), swap_remove(i), pop(), atau into_iter() untuk semuanya. Method yang menerima self (bukan &self) memiliki struct-nya dan boleh memindahkan field-field-nya keluar:
struct User { name: String,}
impl User { fn into_name(self) -> String { self.name }}
fn main() { let mut queue = vec![String::from("email"), String::from("backup"), String::from("report")]; let first = queue.remove(0); println!("Mengerjakan {first}, sisa: {queue:?}");
let users = vec![User { name: String::from("Ferris") }, User { name: String::from("Corro") }]; let names: Vec<String> = users.into_iter().map(|u| u.into_name()).collect(); println!("{names:?}");}Output:
Mengerjakan email, sisa: ["backup", "report"]["Ferris", "Corro"]into_iter() menghabiskan vektornya, jadi kamu tidak bisa memakai users sesudahnya (itu akan jadi E0382). Awalan into_ pada into_name adalah kebiasaan penamaan di Rust: ia memberi tahu pembaca “ini memakai habis nilainya”. remove(0) menggeser semua item lain ke depan; kalau urutan tidak penting, swap_remove lebih cepat. Lihat Menghapus elemen.
Di mana biasanya kamu melihatnya
Section titled “Di mana biasanya kamu melihatnya”Mengumpulkan field dalam loop for atas &users. for u in &users { names.push(u.name); } gagal dengan cannot move out of `u.name` which is behind a shared reference. Perbaikannya: names.push(u.name.clone()), atau loop dengan for u in users kalau kamu tidak butuh users sesudahnya, atau kumpulkan borrow-nya: users.iter().map(|u| u.name.as_str()).collect::<Vec<&str>>().
Memanggil .unwrap() pada field Option lewat sebuah referensi. config.title.unwrap() dengan config: &Config gagal, karena unwrap menerima Option-nya by value (mengambil ownership). Compiler menyarankan perbaikannya: config.title.as_ref().unwrap() (memberi &String), atau config.title.as_deref() untuk Option<&str>.
Memindahkan variabel yang ditangkap di dalam closure Fn. Closure yang mungkin dipanggil berkali-kali tidak boleh menyerahkan apa yang ia tangkap: cannot move out of `report`, a captured variable in an `Fn` closure. Perbaikannya: pinjam di dalam closure (&report), clone di dalamnya, atau, kalau closure itu hanya berjalan sekali, ubah bound-nya menjadi FnOnce. Lihat Fn, FnMut, dan FnOnce.
Dereference sebuah Rc. let s: String = *shared; menghasilkan cannot move out of an `Rc`, karena owner lain mungkin masih memakai nilainya. Perbaikannya: pinjam (&*shared atau cukup &shared), clone, atau pakai Rc::unwrap_or_clone(shared). Box berbeda: kamu yang memilikinya, jadi let s = *boxed; bisa. Lihat Smart pointer.
Error terkait
Section titled “Error terkait”- E0382: memakai nilai yang sudah dipindahkan. Kamu boleh melakukan move, tapi sesudahnya memakai nilainya lagi.
- E0594: tidak bisa melakukan assignment lewat referensi
&. Aturan “kamu hanya meminjam ini” yang sama, tapi untuk menulis, bukan memindahkan. - E0596: tidak bisa meminjam sebagai mutable, misalnya saat
mem::take(&mut self.x)dipakai di method yang hanya punya&self. - E0308: tipe tidak cocok,
expected String, found &String, yang sering muncul tepat setelah kamu beralih ke borrow. Kembalikan&str, atau panggil.clone(). - E0515: tidak bisa mengembalikan referensi ke variabel lokal, saat perbaikan berbasis borrow menunjuk ke data yang sebentar lagi di-drop.
- Semua error compiler: daftar lengkap halaman error.
Kuis singkat
Section titled “Kuis singkat”Kuis singkat
1. let first = names[0]; dengan names: Vec<String> menghasilkan E0507. Kalau kamu hanya mencetak first, apa perbaikan terbaiknya?
2. Method fn next(&mut self) -> Option<Task> harus menyerahkan self.pending (sebuah Option<Task>). Mana yang lolos kompilasi?
3. Kenapa let n = nums[0]; tidak masalah untuk nums: Vec<i32>, tapi bermasalah untuk Vec<String>?
Skor: 0 / 3
Bacaan lebih lanjut
Section titled “Bacaan lebih lanjut”- E0507 — Rust error codes index (bahasa Inggris): penjelasan resminya, termasuk perbaikan dengan
mem::replace. - What Is Ownership? — The Rust Book (bahasa Inggris): move,
Clone, danCopy, ide-ide di balik error ini. - References and Borrowing — The Rust Book (bahasa Inggris): apa yang boleh dan tidak boleh kamu lakukan lewat
&dan&mut. std::mem::take— dokumentasi standard library (bahasa Inggris): ambil nilai dari&mutdan tinggalkan nilai default-nya; saudaranya,mem::replace, membuatmu bisa memilih penggantinya.Option— dokumentasi standard library (bahasa Inggris):as_ref,as_deref, dantake, lengkap dengan contoh.