Lỗi Gặp Phải
error[E0515]: cannot return value referencing local variable `result`
--> src/main.rs:5:5
|
5 | &result
| ^------
| ||
| |`result` is borrowed here
| returns a value referencing data owned by the current function
Lỗi này có nghĩa là bạn đang cố trả về một tham chiếu đến thứ gì đó mà chính hàm đang sở hữu. Ngay khi hàm trả về, biến cục bộ đó sẽ bị hủy — vì vậy mọi tham chiếu đến nó sẽ lập tức trỏ vào vùng nhớ đã được giải phóng. Borrow checker của Rust phát hiện điều này trước khi code của bạn được thực thi.
Nguyên Nhân Gốc Rễ
Biến cục bộ sẽ chết khi hàm chứa nó trả về. Đơn giản vậy thôi. Quy tắc của Rust rất rõ ràng: một tham chiếu không thể tồn tại lâu hơn dữ liệu mà nó trỏ đến. Trả về tham chiếu đến biến cục bộ đồng nghĩa với việc bạn sẽ có một dangling pointer — đúng loại lỗi khiến các chương trình C bị crash và làm hỏng bộ nhớ một cách âm thầm. Rust đơn giản là từ chối biên dịch.
Trường hợp điển hình trông như thế này:
fn get_greeting(name: &str) -> &str {
let result = format!("Hello, {}!", name); // result là một String cục bộ
&result // LỖI: result bị hủy ở đây, tham chiếu sẽ bị treo lơ lửng
}
Lỗi từ compiler:
error[E0515]: cannot return value referencing local variable `result`
--> src/main.rs:3:5
|
3 | &result
| ^------
| |`result` is borrowed here
| returns a value referencing data owned by the current function
Các Cách Sửa Từng Bước
Cách 1: Trả Về Kiểu Owned Thay Vì Tham Chiếu
Nếu hàm tạo ra dữ liệu, hãy trả về trực tiếp dữ liệu đó. Không cần tham chiếu.
// Trước (lỗi)
fn get_greeting(name: &str) -> &str {
let result = format!("Hello, {}!", name);
&result // E0515
}
// Sau (đã sửa)
fn get_greeting(name: &str) -> String {
format!("Hello, {}!", name) // Trả về String owned trực tiếp
}
Pattern tương tự áp dụng cho các collection:
// Trước (lỗi)
fn even_numbers(input: &[i32]) -> &[i32] {
let result: Vec<i32> = input.iter().filter(|&&x| x % 2 == 0).cloned().collect();
&result // E0515
}
// Sau (đã sửa)
fn even_numbers(input: &[i32]) -> Vec<i32> {
input.iter().filter(|&&x| x % 2 == 0).cloned().collect()
}
Cách 2: Trả Về Tham Chiếu Đến Tham Số Đầu Vào
Không tạo dữ liệu mới mà chỉ chọn hoặc cắt từ đầu vào? Hãy tham chiếu trực tiếp vào đầu vào — không cần bản sao cục bộ.
// Trước (lỗi — tạo bản sao cục bộ không cần thiết)
fn first_word(s: &str) -> &str {
let owned = s.to_string(); // biến cục bộ không cần thiết
owned.split_whitespace().next().unwrap_or(&owned) // E0515
}
// Sau (đã sửa — tham chiếu trực tiếp vào đầu vào)
fn first_word(s: &str) -> &str {
s.split_whitespace().next().unwrap_or("")
// lifetime của giá trị trả về gắn với `s`, hoàn toàn hợp lệ
}
Cách 3: Dùng Cow<'_, str> Khi Cần Cả Hai
Một số hàm có điều kiện trả về hoặc là giá trị mượn hoặc là giá trị mới được tạo ra. Cow (Clone-on-Write) được thiết kế chính xác cho trường hợp này:
use std::borrow::Cow;
fn sanitize(input: &str) -> Cow<'_, str> {
if input.contains('<') {
Cow::Owned(input.replace('<', "<")) // String mới được tạo
} else {
Cow::Borrowed(input) // chỉ mượn bản gốc
}
}
fn main() {
let clean = sanitize("hello <world>");
println!("{}", clean); // hello <world>
}
Cách 4: Tái Cấu Trúc Để Tránh Tạo Dữ Liệu Bên Trong Hàm
Đôi khi cách sửa thực sự là xóa bỏ việc cấp phát không cần thiết. Một hàm tạo ra String cục bộ chỉ để cắt nó thường có thể hoạt động trực tiếp trên đầu vào &str thay thế:
// Trước (lỗi)
fn get_domain(url: &str) -> &str {
let trimmed = url.trim_start_matches("https://").to_string(); // String cục bộ
trimmed.split('/').next().unwrap_or("") // E0515
}
// Sau — cắt trực tiếp trên đầu vào &str, không cần String cục bộ
fn get_domain(url: &str) -> &str {
url.trim_start_matches("https://")
.trim_start_matches("http://")
.split('/')
.next()
.unwrap_or("")
}
Xác Nhận Đã Sửa Xong
Chạy cargo build. Nếu E0515 đã biến mất, bạn đã hoàn thành. Cũng chạy cargo test để xác nhận không có gì bị hỏng:
cargo build
cargo test
Để vòng lặp kiểm tra nhanh hơn, cargo check bỏ qua bước sinh code hoàn toàn và bắt các lỗi kiểu dữ liệu cùng lifetime trong khoảng một nửa thời gian so với build đầy đủ:
cargo check
Hướng Dẫn Quyết Định Nhanh
- Đang tạo dữ liệu mới bên trong hàm? → Trả về kiểu owned (
String,Vec<T>, v.v.) - Chỉ chọn hoặc cắt từ đầu vào? → Trả về tham chiếu đến tham số đầu vào
- Đôi khi tạo, đôi khi không? → Dùng
Cow<'_, str>hoặcCow<'_, [T]> - Caller cần thay đổi kết quả? → Luôn trả về kiểu owned
Những Lỗi Thường Gặp
Thêm lifetime annotation không sửa được lỗi này. Đây là cách thử rất phổ biến:
// Vẫn lỗi — lifetime annotation không kéo dài vòng đời của biến
fn get_greeting<'a>(name: &'a str) -> &'a str {
let result = format!("Hello, {}!", name);
&result // E0515 — annotation không thể cứu một biến cục bộ
}
Lifetime annotation mô tả mối quan hệ giữa các lifetime đã tồn tại. Chúng không tạo ra lifetime mới hay kéo dài phạm vi của biến cục bộ. Dữ liệu phải thực sự tồn tại lâu hơn tham chiếu — không có annotation nào thay đổi được điều đó.

