Error Rust E0373: closure may outlive the current function (pakai move)
E0373 artinya sebuah closure (fungsi anonim) meminjam variabel lokal, padahal closure itu mungkin masih berjalan (atau masih ada) setelah fungsi pemilik variabel itu selesai. Biasanya kamu bertemu error ini pertama kali saat memanggil thread::spawn atau mengembalikan closure dari sebuah fungsi. Kabar baiknya: compiler sudah memberitahu perbaikannya, dan hampir selalu cuma satu kata, move. Halaman ini menunjukkan perbaikannya dulu, baru kemudian menjelaskan alasannya. Untuk error lain di seri ini, lihat Penjelasan error compiler Rust.
Pesan error-nya
Section titled “Pesan error-nya”error[E0373]: closure may outlive the current function, but it borrows `scores`, which is owned by the current function --> src/main.rs:5:32 |5 | let handle = thread::spawn(|| { | ^^ may outlive borrowed value `scores`6 | println!("Scores: {scores:?}"); // ❌ hanya meminjam scores | ------ `scores` is borrowed here |note: function requires argument type to outlive `'static` --> src/main.rs:5:18 |5 | let handle = thread::spawn(|| { | __________________^6 | | println!("Scores: {scores:?}"); // ❌ hanya meminjam scores7 | | }); | |______^help: to force the closure to take ownership of `scores` (and any other referenced variables), use the `move` keyword |5 | let handle = thread::spawn(move || { | ++++Ini program yang menghasilkan error tersebut:
use std::thread;
fn main() { let scores = vec![90, 85, 77]; let handle = thread::spawn(|| { println!("Scores: {scores:?}"); // ❌ hanya meminjam scores }); handle.join().unwrap();}Di kode async, kamu akan melihat error yang sama dengan kata-kata sedikit berbeda: “async block may outlive the current function, but it borrows user_id”. Itu error yang sama, dan perbaikannya juga sama (async move).
Apa artinya
Section titled “Apa artinya”Baca label-labelnya dari atas ke bawah:
^^ may outlive borrowed valuemenunjuk ke closure||. Rust tidak bisa menjamin closure itu sudah selesai sebelummainlanjut ke baris berikutnya.`scores` is borrowed heremenunjuk ke tempat closure memakaiscores. Karenaprintln!hanya perlu membacanya, closure itu menangkap sebuah referensi kescores, bukan vektornya sendiri.function requires argument type to outlive 'staticmenjelaskan kenapa itu jadi masalah:thread::spawnhanya menerima closure yang bisa hidup selama apa pun program membutuhkannya.'staticartinya “tidak meminjam apa pun yang bisa hilang”.
Bayangkan kamu meminjamkan buku catatanmu ke teman dan bilang “kembalikan kalau sudah selesai”, padahal kamu sebentar lagi pindah ke kota lain. Kalau temanmu lambat, buku itu tidak punya rumah untuk dikembalikan. Rust menolak kesepakatan seperti itu dan memintamu memberikan buku itu saja.
Kenapa Rust melakukan ini
Section titled “Kenapa Rust melakukan ini”Closure menangkap variabel dengan cara paling hemat yang masih berfungsi. Kalau ia hanya membaca sebuah variabel, ia meminjamnya dengan &. Itu bagus untuk closure yang langsung dijalankan, seperti yang kamu berikan ke .map() atau .filter(). Kamu bisa membaca lebih lanjut di halaman Closure.
Thread baru itu berbeda. Ia bisa berjalan selama berapa pun, dan main tidak menunggunya kecuali kamu memanggil join. Kalau thread itu memegang referensi ke scores, lalu main selesai duluan, scores akan di-drop dan thread-nya membaca memori yang sudah dibebaskan. Itulah “dangling reference”, salah satu bug paling umum di kode thread C dan C++. Rust menghentikannya saat kompilasi.
Hal yang sama terjadi saat kamu mengembalikan closure. Variabel lokal fungsi (dan parameternya) di-drop saat fungsi itu selesai. Closure yang masih meminjamnya akan menunjuk ke ketiadaan.
Cara memperbaikinya
Section titled “Cara memperbaikinya”Perbaikan 1: Tambahkan move (jawaban yang biasa)
Section titled “Perbaikan 1: Tambahkan move (jawaban yang biasa)”Pilih ini hampir setiap saat. Inilah yang disarankan rustc. move menyuruh closure mengambil ownership (kepemilikan) atas variabel-variabel yang dipakainya, jadi variabel itu ikut pergi bersama closure-nya:
use std::thread;
fn main() { let scores = vec![90, 85, 77]; let handle = thread::spawn(move || { let total: i32 = scores.iter().sum(); println!("Nilai: {scores:?}, total {total}"); }); handle.join().unwrap();}Output:
Nilai: [90, 85, 77], total 252Perbaikan yang sama berlaku untuk fungsi yang mengembalikan closure. Tanpa move, |x| x + n meminjam parameter n, yang sudah hilang begitu make_adder selesai. Dengan move, setiap closure menyimpan salinan n-nya sendiri:
fn make_adder(n: i32) -> impl Fn(i32) -> i32 { move |x| x + n}
fn main() { let add_five = make_adder(5); let add_ten = make_adder(10); println!("{}", add_five(1)); println!("{}", add_ten(1));}Output:
611move tidak mengubah trait closure yang kamu dapat. Closure move yang hanya membaca datanya tetap Fn dan bisa dipanggil berkali-kali.
Perbaikan 2: Clone dulu, kalau kamu masih butuh nilainya
Section titled “Perbaikan 2: Clone dulu, kalau kamu masih butuh nilainya”Setelah move, variabelnya menjadi milik closure. Kalau main memakainya lagi nanti, kamu akan mendapat E0382 (“borrow of moved value”), dan rustc menyarankan untuk meng-clone nilainya sebelum dipindahkan. Buat salinan untuk closure dan simpan aslinya:
use std::thread;
fn main() { let guests = vec![String::from("Ana"), String::from("Ben")]; let guests_for_thread = guests.clone();
let handle = thread::spawn(move || { println!("Thread melihat {} tamu", guests_for_thread.len()); }); handle.join().unwrap();
println!("Main masih punya: {guests:?}");}Output:
Thread melihat 2 tamuMain masih punya: ["Ana", "Ben"]Meng-clone daftar kecil itu murah dan sama sekali tidak masalah. Kalau datanya besar, atau banyak thread membutuhkannya, pakai Arc (Perbaikan 3) supaya semuanya berbagi satu salinan.
Perbaikan 3: Bagikan dengan Arc ke beberapa thread
Section titled “Perbaikan 3: Bagikan dengan Arc ke beberapa thread”Pilih ini kalau beberapa thread perlu membaca data yang sama. Arc (“atomically reference counted”) membuat banyak pemilik bisa berbagi satu nilai. Arc::clone hanya menyalin pointer dan menaikkan sebuah penghitung; datanya sendiri tidak disalin:
use std::sync::Arc;use std::thread;
fn main() { let prices = Arc::new(vec![4.5, 2.0, 3.5]); let mut handles = Vec::new();
for discount in [0.0, 0.1, 0.5] { let prices = Arc::clone(&prices); // satu handle per thread handles.push(thread::spawn(move || { let total: f64 = prices.iter().sum(); total * (1.0 - discount) })); }
for handle in handles { println!("Total: {:.2}", handle.join().unwrap()); } println!("Harga: {prices:?}");}Output:
Total: 10.00Total: 9.00Total: 5.00Harga: [4.5, 2.0, 3.5]Arc saja hanya memberi akses baca bersama. Kalau thread-thread itu juga perlu mengubah datanya, bungkus menjadi Arc<Mutex<T>>. Lihat Konkurensi dan Shared state.
Perbaikan 4: Pinjam dengan aman memakai std::thread::scope
Section titled “Perbaikan 4: Pinjam dengan aman memakai std::thread::scope”Pilih ini kalau thread-thread itu hanya butuh datanya untuk pekerjaan singkat dan kamu lebih suka meminjam daripada memindahkan atau meng-clone. Thread yang dimulai di dalam thread::scope dijamin selesai sebelum scope selesai, jadi meminjam variabel lokal itu aman dan tidak perlu move:
use std::thread;
fn main() { let scores = vec![90, 85, 77, 64]; let (first, second) = scores.split_at(2);
let (a, b) = thread::scope(|s| { let h1 = s.spawn(|| first.iter().sum::<i32>()); let h2 = s.spawn(|| second.iter().sum::<i32>()); (h1.join().unwrap(), h2.join().unwrap()) }); // kedua thread sudah selesai di sini
println!("{a} + {b} = {}", a + b); println!("Masih milik kita: {scores:?}");}Output:
175 + 141 = 316Masih milik kita: [90, 85, 77, 64]Scoped thread adalah jawaban paling rapi untuk pola “bagi pekerjaannya, lalu pakai hasilnya”. Pelajaran Shared state dimulai dengan topik ini.
Di mana biasanya kamu melihatnya
Section titled “Di mana biasanya kamu melihatnya”Membuat thread di dalam perulangan. Bahkan angka biasa pun dipinjam kalau closure-nya hanya membacanya:
for worker in 1..=3 { handles.push(thread::spawn(|| { println!("Worker {worker} started"); // ❌ meminjam `worker` }));}Perbaikan: thread::spawn(move || ...). worker adalah i32, jadi move cukup menyalinnya ke setiap thread.
Mengembalikan closure dalam Box. Box::new(|| greeting.clone()) di dalam fungsi yang mengembalikan Box<dyn Fn() -> String> meminjam variabel lokal greeting, dan rustc menambahkan note: closure is returned here. Perbaikan: Box::new(move || greeting.clone()). Selengkapnya di Fn, FnMut, dan FnOnce.
Task async (Tokio dan sejenisnya). tokio::spawn bekerja seperti thread::spawn: task-nya bisa berjalan lama setelah fungsi saat ini selesai, jadi ia butuh data 'static. Menulis tokio::spawn(async { ... }) dan memakai variabel lokal di dalamnya memberi “async block may outlive the current function”. Perbaikan: tokio::spawn(async move { ... }), lalu clone atau Arc::clone apa pun yang masih kamu butuhkan di luar task. Lihat Async/Await.
Error terkait
Section titled “Error terkait”- E0382: borrow of moved value: muncul tepat setelah kamu menambahkan
movelalu memakai variabelnya lagi di luar closure. Clone dulu (Perbaikan 2) atau pakaiArc(Perbaikan 3). - E0597: borrowed value does not live long enough: sepupu dekatnya. Sebuah referensi masih dipakai setelah nilai yang ditunjuknya di-drop.
- E0515: cannot return reference to local variable: masalah “sudah hilang saat fungsi selesai” yang sama, tapi dengan referensi biasa, bukan closure.
- E0106: missing lifetime specifier: muncul saat kamu mencoba mengembalikan referensi (atau tipe closure) yang meminjam sesuatu.
- Semua error compiler: daftar lengkap halaman error.
Kuis singkat
Section titled “Kuis singkat”Kuis singkat
1. Sebuah closure thread mencetak name tanpa move. Kenapa kamu mendapat E0373?
2. Kamu sudah menambahkan move, tapi main masih perlu mencetak vektornya setelah spawn. Apa perbaikan paling sederhana?
3. Alat mana yang membuat thread bisa meminjam variabel lokal tanpa move?
Skor: 0 / 3
Bacaan lebih lanjut
Section titled “Bacaan lebih lanjut”- E0373 — Rust error codes index (bahasa Inggris): penjelasan resmi error ini, dengan perbaikan
move. - Using Threads to Run Code Simultaneously — The Rust Book (bahasa Inggris): bagian “Using move Closures with Threads” membahas error yang persis sama.
- Closures — The Rust Book (bahasa Inggris): bagaimana closure menangkap variabel lewat referensi, referensi mutable, atau nilai.
- Keyword
move— dokumentasi standard library (bahasa Inggris): apa yang dilakukanmovepada tangkapan closure, dengan contoh singkat. std::thread::scope— dokumentasi standard library (bahasa Inggris): scoped thread yang bisa meminjam data lokal dengan aman.