Sửa lỗi Rust error[E0382]: use of moved value — Ownership và Move Semantics

intermediate🦀 Rust2026-03-23| Rust 1.60+, mọi nền tảng (Linux, macOS, Windows), trình biên dịch rustc

Error Message

error[E0382]: use of moved value
#rust#ownership#move#clone

TL;DR

Bạn đã sử dụng một giá trị sau khi nó đã được chuyển (move) vào một biến hoặc hàm khác. Hệ thống ownership của Rust làm mất hiệu lực binding gốc sau khi move. Hai cách sửa nhanh: gọi .clone() trước khi move, hoặc truyền tham chiếu (&value) thay vì truyền trực tiếp giá trị.

// LỖI
let s = String::from("hello");
let s2 = s;         // s bị move tại đây
println!("{}", s); // error[E0382]: use of moved value: `s`

// FIX 1: clone
let s = String::from("hello");
let s2 = s.clone();
println!("{}", s); // OK

// FIX 2: borrow
let s = String::from("hello");
let s2 = &s;
println!("{}", s); // OK

Chuyện gì đang thực sự xảy ra

Mỗi giá trị được cấp phát trên heap trong Rust chỉ có đúng một owner tại một thời điểm. Khi gán s2 = s, hoặc truyền s vào một hàm — quyền sở hữu được chuyển giao. Rust gọi đây là move. Sau khi move, s không còn tồn tại nữa. Trình biên dịch sẽ không cho phép bạn dùng một biến đã "ma".

Đây là toàn bộ thông báo lỗi:

error[E0382]: use of moved value: `s`
 --> src/main.rs:4:20
  |
2 |     let s2 = s;
  |              - value moved here
3 |     println!("{}", s);
  |                    ^ value used here after move
  |
  = note: move occurs because `s` has type `String`, which does not implement the `Copy` trait

Dòng cuối cùng là mấu chốt. Các kiểu triển khai trait Copy — số nguyên, boolean, char, tuple của các kiểu Copy — sẽ được sao chép ngầm thay vì move. String, Vec, HashMap, và hầu hết các kiểu heap không triển khai Copy, nên chúng sẽ bị move.

Các tình huống thường gặp và cách xử lý

Tình huống 1: Move vào một hàm

fn save_to_db(name: String) { /* tiêu thụ name */ }

let name = String::from("Alice");
save_to_db(name);        // name bị move vào save_to_db
println!("{}", name);   // error[E0382]: use of moved value: `name`

Cách sửa A: Thay đổi signature của hàm để mượn (borrow) thay vì sở hữu:

fn save_to_db(name: &str) { /* mượn name */ }

save_to_db(&name);
println!("{}", name); // OK

Cách sửa B: Clone trước khi truyền nếu bạn không thể thay đổi hàm:

save_to_db(name.clone());
println!("{}", name); // OK

Tình huống 2: Move trong vòng lặp

Lỗi này hay xuất hiện lúc 2 giờ sáng:

let data = vec![1, 2, 3];
for _ in 0..3 {
    process(data); // error[E0382]: use of moved value: `data`
                   // bị move ở vòng lặp đầu tiên, mất sau đó
}

fn process(v: Vec<i32>) { /* tiêu thụ v */ }

Cách sửa A: Truyền tham chiếu nếu hàm không cần ownership:

fn process(v: &[i32]) { /* mượn v */ }

for _ in 0..3 {
    process(&data); // OK
}

Cách sửa B: Clone bên trong vòng lặp chỉ khi hàm bắt buộc phải sở hữu giá trị:

for _ in 0..3 {
    process(data.clone());
}

Tình huống 3: Move vào closure

let config = Config::new();
let handler = move || {
    println!("{:?}", config); // config bị move vào closure
};
println!("{:?}", config); // error[E0382]: use of moved value

Cách sửa: Clone trước khi closure move bắt giữ nó, để mỗi bên có bản sao riêng:

let config = Config::new();
let config_clone = config.clone();
let handler = move || {
    println!("{:?}", config_clone);
};
println!("{:?}", config); // OK

Tình huống 4: Partial move từ các trường của struct

struct User {
    name: String,
    email: String,
}

let user = User {
    name: String::from("Alice"),
    email: String::from("alice@example.com"),
};

let name = user.name;       // partial move!
println!("{}", user.email); // OK — email chưa bị move
println!("{}", user.name);  // error[E0382]: partial move

Cách sửa: Clone trường đó để struct vẫn còn nguyên vẹn:

let name = user.name.clone();
println!("{}", user.name); // OK

Tình huống 5: match tiêu thụ một giá trị

let opt = Some(String::from("data"));
match opt {
    Some(s) => println!("Got: {}", s), // s bị move ra khỏi opt
    None => {}
}
println!("{:?}", opt); // error[E0382]

Cách sửa: Match trên tham chiếu — thêm & trước opt:

match &opt {
    Some(s) => println!("Got: {}", s),
    None => {}
}
println!("{:?}", opt); // OK

Khi clone() quá tốn kém

Clone một Vec 100 MB trong vòng lặp nóng sẽ làm giảm hiệu năng nghiêm trọng. Trước khi dùng .clone(), hãy xem xét các lựa chọn thay thế sau:

  • Arc — chia sẻ quyền sở hữu giữa các thread. Clone nó chỉ tăng một bộ đếm nguyên tử, không sao chép dữ liệu.
  • Rc — ý tưởng tương tự, chỉ dùng cho single-threaded.
  • Cow — mượn cho đến khi cần thay đổi, rồi mới clone theo kiểu lazy.
use std::sync::Arc;

let data = Arc::new(vec![1, 2, 3]);
let data2 = Arc::clone(&data); // clone con trỏ rẻ, không sao chép dữ liệu

std::thread::spawn(move || {
    println!("{:?}", data2);
});
println!("{:?}", data); // OK

Kiểm tra kết quả

Sau khi áp dụng cách sửa, kiểm tra xem lỗi E0382 đã biến mất chưa:

cargo build 2>&1 | grep "E0382"

Không có output nghĩa là bạn đã xong. Để build lại hoàn toàn từ đầu:

cargo clean && cargo build

Hãy chạy Clippy nữa — nó thường gợi ý giải pháp chuẩn hơn so với việc clone thủ công:

cargo clippy

Hướng dẫn lựa chọn

  • Hàm chỉ đọc giá trị → truyền &T
  • Hàm cần chỉnh sửa → truyền &mut T
  • Hàm cần ownership và bạn vẫn cần bản gốc → .clone()
  • Giá trị chia sẻ giữa các thread → Arc<T>
  • Giá trị chia sẻ trong code single-threaded → Rc<T>
  • Kiểu nhỏ và chỉ nằm trên stack → derive Copy
#[derive(Copy, Clone)]
struct Point { x: f64, y: f64 }

let p = Point { x: 1.0, y: 2.0 };
let p2 = p; // được sao chép, không phải move
println!("{}", p.x); // OK

Related Error Notes