Vấn đềBạn đang xây dựng một struct trong Rust và cần trả về một trường từ một phương thức. Mọi thứ trông có vẻ ổn, nhưng trình biên dịch lại báo lỗi. Lỗi này xảy ra khi bạn cố gắng di chuyển (move) một kiểu dữ liệu không phải Copy—như String hoặc Vec—ra khỏi một phương thức chỉ có tham chiếu dùng chung (&self) tới dữ liệu.
Thông báo lỗi chi tiết```
error[E0507]: cannot move out of self.name which is behind a shared reference
--> src/main.rs:10:9
|
10 | self.name
| ^^^^^^^^^ move occurs because self.name has type String, which does not implement the Copy trait
## Nguyên nhân gốc rễTrình kiểm tra mượn (borrow checker) của Rust rất nghiêm ngặt. Khi một phương thức nhận `&self`, nó chỉ có quyền xem dữ liệu chứ không được lấy đi. Nếu bạn cố gắng trả về trực tiếp một trường `String`, bạn đang cố gắng chiếm đoạt quyền sở hữu từ struct. Nếu Rust cho phép điều này, struct ban đầu sẽ bị "rỗng"—chứa một trường đã bị giải phóng hoặc chưa được khởi tạo—trong khi phần còn lại của chương trình vẫn nghĩ rằng struct đó hợp lệ. Điều này dẫn đến lỗi bộ nhớ, vì vậy trình biên dịch sẽ chặn bạn ngay lập tức.
## Các bước khắc phục### 1. Clone dữ liệuLựa chọn đầu tiên rất đơn giản: clone dữ liệu. Việc này tạo ra một bản sao hoàn toàn mới trên heap. Trường ban đầu vẫn an toàn bên trong struct, và phía gọi phương thức sẽ nhận được một phiên bản độc lập của riêng họ. Hãy lưu ý rằng `.clone()` có thể tốn kém nếu bạn thực hiện hàng nghìn lần mỗi giây trong một vòng lặp hẹp.
struct User { name: String, }
impl User { // THẤT BẠI: không thể di chuyển dữ liệu ra khỏi self.name // fn get_name(&self) -> String { self.name }
// THÀNH CÔNG: Trả về một bản sao mới
fn get_name(&self) -> String {
self.name.clone()
}
}
### 2. Mượn thay vì Di chuyểnTại sao phải sở hữu dữ liệu khi bạn chỉ cần xem nó? Nếu phía gọi chỉ cần đọc giá trị, hãy thay đổi kiểu trả về thành một tham chiếu. Đây là cách sửa lỗi hiệu quả nhất vì nó không tốn bộ nhớ cấp phát và không cần sao chép.
impl User { // THÀNH CÔNG: Trả về một tham chiếu đến chuỗi hiện có fn get_name(&self) -> &str { &self.name } }
### 3. Hoán đổi hoặc Lấy đi (Sử dụng std::mem)Nếu bạn có một tham chiếu **có thể thay đổi** (`&mut self`), bạn có thể thực hiện một thao tác "hoán đổi". Sử dụng `std::mem::take` để lấy giá trị ra trong khi để lại một giá trị mặc định (như một String trống) tại vị trí đó. Đây là một mô hình phổ biến cho `Vec` hoặc `String` vì `String::new()` không thực sự cấp phát bộ nhớ cho đến khi bạn đưa nội dung vào đó.
use std::mem;
struct Session { data: String, }
impl Session { fn take_data(&mut self) -> String { // Thay thế self.data bằng "" và trả về nội dung ban đầu mem::take(&mut self.data) } }
### 4. Tiêu thụ StructĐôi khi, phương thức này là hành động cuối cùng của struct đó. Bằng cách thay đổi `&self` thành `self`, bạn di chuyển quyền sở hữu của toàn bộ đối tượng vào phương thức. Khi bạn đã sở hữu struct, bạn có thể tháo rời nó và trả về các thành phần bên trong một cách tự do.
impl User { // THÀNH CÔNG: Tiêu thụ instance User, cho phép di chuyển quyền sở hữu fn into_name(self) -> String { self.name } }
## Xác minhChạy kiểm tra nhanh để xem borrow checker đã hài lòng chưa:
cargo check
Nếu kết quả sạch lỗi, bạn đã giải được câu đố về quyền sở hữu. Nếu bạn chọn cách `.clone()`, hãy xác minh rằng logic của bạn không yêu cầu những thay đổi trong chuỗi được trả về phải phản ánh ngược lại struct ban đầu.
## Phòng ngừa và Thực hành tốt nhất- **Ưu tiên Tham chiếu:** Mặc định trả về `&str` hoặc `&[T]`. Nó nhanh hơn và linh hoạt hơn cho người gọi.- **Derive Copy:** Nếu struct của bạn chỉ chứa dữ liệu nhỏ, được cấp phát trên stack như `u32` hoặc `f64`, hãy thêm `#[derive(Copy, Clone)]` để tránh hoàn toàn các lỗi này.- **Bọc trong Option:** Nếu bạn thường xuyên cần "lấy" một trường, hãy bọc nó trong một `Option`. Sau đó, bạn có thể sử dụng `self.field.take()`, cách này gọn gàng hơn `std::mem::replace`.Khi tái cấu trúc các máy trạng thái phức tạp, tôi thường sử dụng [Hash Generator](https://toolcraft.app/en/tools/developer/hash-generator) để xác minh tính toàn vẹn của dữ liệu. Ví dụ, nếu tôi đang di chuyển một buffer 10MB ra khỏi một struct, tôi sẽ tạo checksum trước và sau khi chuyển đổi. Điều này đảm bảo rằng việc chuyển đổi quyền sở hữu không vô tình làm hỏng dữ liệu hoặc để lại hệ thống trong trạng thái không nhất quán.

