Lewati ke konten

Error Rust E0382: borrow of moved value (nilai sudah dipindah)

E0382 artinya kamu sudah menyerahkan sebuah nilai (kamu memindahkannya, istilahnya move), lalu mencoba memakainya lagi. Ini error yang paling sering ditemui orang di minggu pertama belajar Rust, dan hampir selalu bisa diperbaiki dengan satu baris. Halaman ini menunjukkan perbaikannya dulu, baru kemudian menjelaskan apa yang terjadi. Untuk error lain di seri ini, lihat Penjelasan error compiler Rust.

error[E0382]: borrow of moved value: `name`
--> src/main.rs:8:25
|
6 | let name = String::from("Ferris");
| ---- move occurs because `name` has type `String`, which does not implement the `Copy` trait
7 | greet(name); // name di-move ke dalam greet di sini
| ---- value moved here
8 | println!("Goodbye, {name}!"); // ❌ name sudah tidak ada
| ^^^^ value borrowed here after move
|
note: consider changing this parameter type in function `greet` to borrow instead if owning the value isn't necessary
--> src/main.rs:1:16
|
1 | fn greet(name: String) {
| ----- ^^^^^^ this parameter takes ownership of the value
help: consider cloning the value if the performance cost is acceptable
|
7 | greet(name.clone()); // name di-move ke dalam greet di sini
| ++++++++

Compiler menyebut tiga hal: nilainya di-move di baris 7, lalu dipinjam lagi di baris 8. Ia juga menyarankan dua jalan keluar: ubah parameter greet menjadi borrow, atau clone nilainya.

Ini program yang menghasilkan error tersebut:

fn greet(name: String) {
println!("Hello, {name}!");
}
fn main() {
let name = String::from("Ferris");
greet(name); // name di-move ke dalam greet di sini
println!("Goodbye, {name}!"); // ❌ name sudah tidak ada
}

Kamu mungkin juga melihat kalimat “use of moved value”. Itu error yang sama. Rust bilang “borrow” kalau baris berikutnya hanya membaca nilainya (seperti println!), dan “use” kalau baris itu mencoba memindahkannya lagi (seperti let other = name;).

Baca tiga label itu dari atas ke bawah. Ketiganya menceritakan semuanya:

  1. Baris 6: name adalah String, dan String bukan Copy. Jadi kalau diberikan ke tempat lain, ia dipindahkan (move).
  2. Baris 7: greet(name) memindahkan string itu ke dalam fungsi. Sekarang greet yang memilikinya, dan string itu dibuang saat greet selesai.
  3. Baris 8: kamu mencoba membaca name lagi, padahal main sudah tidak memilikinya.

Bayangkan kamu memberikan satu-satunya kunci rumahmu ke seorang teman. Begitu sudah diserahkan, kamu tidak bisa membuka pintu dengan kunci itu, meskipun kamu ingat dulu pernah memegangnya.

Setiap nilai di Rust punya tepat satu owner (pemilik). Saat owner-nya hilang, nilainya dibersihkan. Dengan cara inilah Rust membebaskan memori tanpa garbage collector. Aturannya dijelaskan di halaman Ownership (kepemilikan).

Kalau dua variabel sama-sama memiliki String yang sama, keduanya akan mencoba membebaskan memori yang sama di akhir. Itu namanya “double free”, bug sungguhan yang bisa membuat program C dan C++ crash. Rust mencegahnya dengan membuat assignment dan pemanggilan fungsi memindahkan nilai, sehingga selalu hanya ada satu owner.

Tipe kecil dan sederhana seperti i32, bool, dan char adalah Copy: saat diberikan ke tempat lain, yang terjadi adalah salinan murah, dan kamu tidak akan pernah melihat E0382 untuk tipe-tipe itu. Tipe yang memiliki memori di heap, seperti String, Vec, dan kebanyakan struct, dipindahkan.

Pilih ini kalau fungsinya hanya perlu melihat nilainya. Hampir selalu inilah perbaikan yang tepat. Ubah parameternya menjadi referensi dan berikan &name:

fn greet(name: &str) {
println!("Halo, {name}!");
}
fn main() {
let name = String::from("Ferris");
greet(&name); // pinjamkan, jangan diserahkan
println!("Sampai jumpa, {name}!"); // ✅ masih milik kita
}

Output:

Halo, Ferris!
Sampai jumpa, Ferris!
▶ Coba sendiri

&String juga bisa, tapi &str lebih umum dipakai karena juga menerima string literal. Lihat Borrowing (peminjaman) dan String. Kalau fungsinya perlu mengubah nilainya, pakai &mut.

Pilih ini kalau kode lain memang butuh salinannya sendiri, misalnya untuk disimpan di struct atau Vec, sementara kamu masih butuh yang asli. .clone() membuat salinan penuh yang berdiri sendiri:

struct Player {
name: String,
score: u32,
}
fn main() {
let name = String::from("Ferris");
let player = Player { name: name.clone(), score: 0 };
println!("{} mulai dengan {} poin", player.name, player.score);
println!("Nama asli: {name}");
}

Output:

Ferris mulai dengan 0 poin
Nama asli: Ferris
▶ Coba sendiri

Jujur saja: meng-clone string pendek atau Vec kecil itu murah, dan sama sekali tidak apa-apa, apalagi selagi kamu masih belajar. Baru pikirkan soal ini kalau kamu meng-clone data besar di dalam loop yang sangat sering berjalan. Kalau borrow (Perbaikan 1) sudah cukup, pilih itu.

Pilih ini untuk struct kecil yang hanya berisi nilai sederhana, seperti koordinat, warna, atau ID. Tambahkan #[derive(Clone, Copy)], dan struct itu akan disalin, bukan dipindahkan:

#[derive(Debug, Clone, Copy)]
struct Point {
x: i32,
y: i32,
}
fn show(p: Point) {
println!("({}, {})", p.x, p.y);
}
fn main() {
let start = Point { x: 0, y: 3 };
show(start); // menyalin start
show(start); // ✅ bisa dipakai lagi
println!("{:?}", start);
}

Output:

(0, 3)
(0, 3)
Point { x: 0, y: 3 }
▶ Coba sendiri

Copy hanya bisa dipakai kalau setiap field juga Copy. Struct dengan field String atau Vec tidak bisa Copy; untuk itu pakai Clone dan panggil .clone(). Selengkapnya di Trait yang sering dipakai.

Pilih ini kalau sebuah fungsi menerima nilai, mengubahnya, dan kamu ingin hasilnya. Fungsi itu mengembalikan nilainya, lalu kamu menyimpannya lagi:

fn add_signature(mut letter: String) -> String {
letter.push_str("\n-- Ferris");
letter // kembalikan
}
fn main() {
let letter = String::from("Halo, Rustacean,");
let letter = add_signature(letter); // ambil kembali
println!("{letter}");
}

Output:

Halo, Rustacean,
-- Ferris
▶ Coba sendiri

Cara ini cocok untuk fungsi bergaya “builder”. Untuk perubahan sederhana di tempat, fn add_signature(letter: &mut String) sering lebih mudah dibaca.

Loop for atas sebuah Vec. for task in tasks memindahkan seluruh vektor ke dalam loop, jadi kamu tidak bisa memakai tasks sesudahnya:

for task in tasks {
println!("Doing: {task}");
}
println!("{} tasks done", tasks.len()); // ❌ `tasks` moved due to this implicit call to `.into_iter()`

Perbaikannya: loop atas sebuah borrow, for task in &tasks. Lihat For di Rust dan Iterator.

Memasukkan nilai ke Vec atau struct. names.push(name) dan Order { customer } sama-sama memindahkan nilainya ke dalam. Memakai name atau customer sesudahnya akan gagal. Perbaikannya: baca lagi dari “rumah barunya” (names[0], order.customer), kerjakan hal lain sebelum move, atau push hasil .clone().

Move di dalam loop. Kalau log.push(message) ada di dalam loop, putaran pertama memindahkan message, dan putaran kedua sudah tidak punya apa-apa. Compiler akan bilang value moved here, in previous iteration of loop (nilai sudah dipindah di putaran sebelumnya). Perbaikannya: push message.clone(), atau buat nilainya di dalam loop supaya setiap putaran punya nilai baru.

Thread dan closure move. thread::spawn(move || ...) memindahkan setiap variabel yang ditangkap ke thread baru. Perbaikannya: clone datanya sebelum spawn, atau bagikan dengan Arc. Lihat Konkurensi dan Shared state.

  • E0502: kamu meminjam sebuah nilai, lalu mencoba mengubahnya selagi borrow itu masih dipakai.
  • E0499: dua borrow mutable atas nilai yang sama pada saat bersamaan.
  • E0106: lifetime specifier tidak ada, sering muncul saat kamu mengembalikan referensi untuk menghindari move.
  • E0308: tipe tidak cocok, misalnya memberikan String padahal yang diharapkan &str setelah kamu beralih ke borrow.
  • Semua error compiler: daftar lengkap halaman error.

Kuis singkat

1. Kamu memanggil save(report) dengan report bertipe String, lalu mencetak report. Kenapa muncul E0382?

2. Sebuah fungsi hanya perlu membaca Vec<String>. Biasanya apa perbaikan terbaiknya?

3. Struct mana yang bisa memakai #[derive(Clone, Copy)]?

Skor: 0 / 3