Tình huống lỗiBạn vừa định nghĩa một struct mới để lưu trữ dữ liệu, nhưng ngay khi bạn cố gắng so sánh hai instance, trình biên dịch sẽ báo lỗi. Điều này thường xảy ra khi sử dụng các toán tử bằng (==) hoặc không bằng (!=) trên các kiểu dữ liệu tùy chỉnh.
Hãy xem đoạn mã sau:
struct User {
id: u32,
username: String,
}
fn main() {
let user1 = User { id: 1, username: String::from("alice") };
let user2 = User { id: 1, username: String::from("alice") };
if user1 == user2 { // Trình biên dịch dừng tại đây
println!("Người dùng giống nhau");
}
}
Chạy lệnh cargo build sẽ kích hoạt một thông báo lỗi màu đỏ quen thuộc:
error[E0369]: binary operation `==` cannot be applied to type `User`
--> src/main.rs:10:14
|
10 | if user1 == user2
| ----- ^^ ---- User
| |
| User
|
note: có thể thiếu một triển khai của `PartialEq` cho `User`
Tại sao điều này xảy raBạn chuyển từ Python hoặc JavaScript sang? Bạn có thể mong đợi runtime tự động so sánh các đối tượng. Tuy nhiên, Rust từ chối đoán ý người dùng. Nó ưu tiên tính tường minh và hiệu suất, vì vậy nó sẽ không tự giả định cách bạn muốn so sánh các cấu trúc dữ liệu phức tạp.
Để sử dụng toán tử ==, kiểu dữ liệu của bạn phải triển khai trait PartialEq. Trait này cung cấp logic mà trình biên dịch cần để kiểm tra tính bằng nhau. Nếu không có nó, Rust không có hướng dẫn nào về việc nên so sánh mọi trường hay chỉ một ID cụ thể.
Cách sửa nhanh: Sử dụng #[derive]Cách nhanh nhất để khắc phục điều này là để Rust tự viết mã cho bạn. Nếu bạn muốn hai instance bằng nhau chỉ khi mọi trường đều khớp, hãy sử dụng một procedural macro.
Chỉ cần thêm #[derive(PartialEq)] phía trên định nghĩa struct của bạn:
#[derive(PartialEq)]
struct User {
id: u32,
username: String,
}
fn main() {
let user1 = User { id: 1, username: String::from("alice") };
let user2 = User { id: 1, username: String::from("alice") };
assert_eq!(user1, user2); // Bây giờ mã đã hoạt động hoàn hảo
}
Yêu cầu quan trọngĐể macro hoạt động, mọi trường bên trong struct của bạn cũng phải triển khai PartialEq. Các kiểu dữ liệu tiêu chuẩn như u32, f32, và String đã thực hiện điều này. Tuy nhiên, nếu struct của bạn chứa một struct Address tùy chỉnh thiếu trait này, trình biên dịch sẽ chỉ ra chính xác trường đó là nguyên nhân gây lỗi.
Cách sửa chính xác: Triển khai thủ côngSo sánh từng trường theo cách tiêu chuẩn không phải lúc nào cũng là lựa chọn đúng. Hãy tưởng tượng một InventoryItem mà chỉ cần mã sku duy nhất là đủ để xác định tính bằng nhau, ngay cả khi timestamp hoặc quantity khác nhau. Trong những trường hợp này, bạn nên triển khai trait một cách thủ công.
struct InventoryItem {
sku: u32,
quantity: i32,
}
impl PartialEq for InventoryItem {
fn eq(&self, other: &Self) -> bool {
self.sku == other.sku
}
}
fn main() {
let item1 = InventoryItem { sku: 1001, quantity: 5 };
let item2 = InventoryItem { sku: 1001, quantity: 500 };
// Kết quả trả về true vì chúng ta chỉ quan tâm đến SKU
if item1 == item2 {
println!("Cùng sản phẩm, mức tồn kho khác nhau");
}
}
PartialEq vs. Eq: Khác biệt là gì?Bạn có thể sẽ thấy trait Eq được nhắc đến cùng với PartialEq. Mặc dù chúng trông giống nhau, nhưng chúng phục vụ các mục đích toán học khác nhau:
- PartialEq: Xử lý các so sánh mà một giá trị có thể không bằng chính nó. Ví dụ, trong toán học số thực dấu phẩy động IEEE 754,
NaN == NaNlà false.- Eq: Đây là một marker trait cho "tính bằng nhau toàn phần" (total equality). Nó cho trình biên dịch biết rằng với mọi giá trịa,a == aphải luôn luôn đúng.Nếu struct của bạn không sử dụng số thực dấu phẩy động (f32hoặcf64), cách tốt nhất là derive cả hai. Điều này cho phép bạn sử dụng kiểu dữ liệu của mình làm key trong mộtHashMap, vốn yêu cầu tính bằng nhau toàn phần.
#[derive(PartialEq, Eq)]
struct Product {
id: u64,
}
Kiểm tra kết quảSau khi bạn đã thêm trait, hãy xác nhận lại bằng hai bước kiểm tra nhanh. Đầu tiên, chạy cargo check để đảm bảo lỗi E0369 đã biến mất. Thứ hai, viết một unit test để xác nhận logic của bạn hoạt động như mong đợi.
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_equality() {
let p1 = Product { id: 500 };
let p2 = Product { id: 500 };
assert!(p1 == p2);
}
}
Chạy lệnh cargo test. Nếu bạn thấy thông báo "ok" màu xanh lá cây, kiểu dữ liệu tùy chỉnh của bạn hiện đã hoàn toàn tương thích với các toán tử so sánh của Rust.

