Tìm hiểu về lỗi
Rust luôn ưu tiên sự an toàn. Theo mặc định, mọi biến bạn tạo đều là bất biến (immutable). Điều này có nghĩa là một khi bạn đã gán một giá trị cho một cái tên bằng từ khóa let, giá trị đó sẽ bị khóa lại. Nếu bạn cố gắng thay đổi nó sau đó, trình biên dịch sẽ ngăn chặn bạn ngay lập tức.
error[E0384]: cannot assign twice to immutable variable `x`
--> src/main.rs:4:5
|
3 | let x = 5;
| - lần gán đầu tiên cho `x`
4 | x = 6;
| ^^^^^ không thể gán hai lần cho biến bất biến
Hãy coi sự khắt khe này như một lưới an toàn. Nó ngăn chặn toàn bộ một nhóm các lỗi mà dữ liệu thay đổi bất ngờ ngoài tầm kiểm soát. Mặc dù ban đầu có vẻ hạn chế, Rust cung cấp cho bạn một số cách đặc thù (idiomatic) để giải quyết vấn đề này.
Nguyên nhân gốc rễ
Trình biên dịch báo lỗi E0384 vì bạn đang cố gắng thay đổi một giá trị đã được cam kết là hằng số. Trong Rust, tính bất biến là nền tảng. Trừ khi bạn nói rõ với trình biên dịch rằng một biến sẽ thay đổi, nó sẽ giả định giá trị đó giữ nguyên trong suốt phạm vi (scope) của nó. Điều này cho phép trình biên dịch tối ưu hóa mã của bạn mạnh mẽ hơn và làm cho logic của bạn dễ theo dõi hơn.
Giải pháp 1: Sử dụng từ khóa mut
Cách nhanh nhất để khắc phục là khai báo biến của bạn là biến có thể thay đổi (mutable). Bằng cách thêm mut, bạn báo hiệu cho cả trình biên dịch và các nhà phát triển khác rằng giá trị này có thể thay đổi.
Ví dụ: Theo dõi một bộ đếm
fn main() {
// Sử dụng 'mut' để cho phép gán lại
let mut retry_count = 0;
println!("Số lần thử hiện tại: {}", retry_count);
retry_count = 1;
println!("Số lần thử đã cập nhật: {}", retry_count);
}
Khi nào nên sử dụng: Hãy chọn mut khi bạn có một mẩu dữ liệu duy nhất thay đổi tự nhiên theo thời gian. Các ví dụ phổ biến bao gồm bộ đếm vòng lặp, bộ tích lũy (accumulators) hoặc bộ đệm (buffers) nhận các khối dữ liệu mới.
Giải pháp 2: Shadowing biến
Đôi khi bạn không cần một biến phải có khả năng thay đổi; bạn chỉ cần chuyển đổi nó. Shadowing cho phép bạn sử dụng lại tên biến bằng cách tạo một liên kết hoàn toàn mới với từ khóa let. Điều này thực tế sẽ "che bóng" (shadow) biến cũ, làm cho nó không thể truy cập được và thay thế bằng biến mới.
Ví dụ: Chuyển đổi dữ liệu
fn main() {
let input = " 42 ";
// Shadowing cho phép chúng ta thay đổi kiểu dữ liệu từ &str sang usize
let input = input.trim();
let input: usize = input.parse().expect("Không phải là một con số!");
println!("Số đã phân tích là: {}", input); // 42
}
Tại sao shadowing lại mạnh mẽ:
- Nó cho phép bạn thay đổi kiểu dữ liệu trong khi vẫn giữ một cái tên mô tả rõ ràng.
- Biến vẫn ở trạng thái bất biến sau lần chuyển đổi cuối cùng. Điều này ngăn chặn các thay đổi vô ý sau đó trong hàm của bạn.
Giải pháp 3: Sử dụng biểu thức để gán
Một cái bẫy phổ biến đối với người mới bắt đầu là cố gắng khởi tạo một biến bên trong khối if/else. Vì các khối if trong Rust là các biểu thức (expressions), bạn không cần phải khai báo một biến rồi mới gán cho nó. Bạn có thể chỉ đơn giản là trả về giá trị trực tiếp từ khối đó.
Cách làm chưa đúng:
let status_code;
if success {
status_code = 200;
} else {
status_code = 404;
}
// Nếu bạn cố gắng thay đổi status_code ở đây, bạn sẽ gặp lỗi E0384
Cách làm chuẩn Rust:
let is_authenticated = true;
// Gán kết quả của toàn bộ biểu thức 'if' cho biến
let status_code = if is_authenticated {
200
} else {
401
};
println!("Phản hồi: {}", status_code);
Mô hình này đảm bảo status_code chỉ được gán một lần. Nó đáp ứng các quy tắc bất biến của trình biên dịch trong khi vẫn giữ cho mã của bạn sạch sẽ và mang tính chức năng (functional).
Xác minh và Kiểm tra
Sau khi bạn áp dụng bản sửa lỗi, hãy xác minh bằng các bước sau:
- Chạy
cargo checktrong terminal của bạn. Lệnh này kiểm tra cú pháp và kiểu dữ liệu mà không tốn công sức xây dựng (build) toàn bộ, giúp bạn tiết kiệm 5–10 giây cho các dự án lớn hơn. - Nếu kiểm tra thành công, hãy thực thi
cargo runđể xác nhận logic của bạn hoạt động như mong đợi. - Chú ý các cảnh báo "unused mut". Nếu Rust cho bạn biết một biến không cần phải là biến có thể thay đổi, hãy xóa từ khóa
mutđể giữ cho mã của bạn an toàn.
Thực hành tốt nhất
Để tránh lỗi E0384, hãy rèn luyện các thói quen sau:
- Mặc định là bất biến: Hãy bắt đầu mọi biến với
let. Chỉ thêmmutkhi trình biên dịch hoặc logic của bạn yêu cầu cụ thể. - Tận dụng Match và If: Sử dụng biểu thức
matchhoặcifđể tính toán giá trị. Cách này hầu như luôn sạch sẽ hơn việc khai báo một biến rồi cập nhật nó bên trong các nhánh. - Giữ phạm vi nhỏ: Nếu một biến bắt buộc phải có khả năng thay đổi, hãy gói logic đó trong một khối nhỏ
{}hoặc một hàm hỗ trợ. Điều này giới hạn khu vực mà giá trị có thể thay đổi, giúp việc gỡ lỗi dễ dàng hơn nhiều.

