Lỗi Rust E0277: the trait bound is not satisfied (chưa triển khai trait)
Lỗi E0277 nghĩa là: bạn dùng một kiểu ở chỗ cần một khả năng mà kiểu đó không có. In nó ra, so sánh nó, cộng nó, chuyển đổi nó, gửi nó sang luồng khác: mỗi việc này đều cần một trait, và kiểu của bạn đang thiếu trait đó. Đây là một trong những lỗi Rust thường gặp nhất, thông báo thường cho bạn biết chính xác cách sửa, và phần lớn thời gian cách sửa chỉ là một dòng. (Trang này thuộc loạt bài Giải thích các lỗi trình biên dịch Rust.)
Thông báo lỗi
Phần tiêu đề “Thông báo lỗi”Dạng mà hầu hết mọi người gặp đầu tiên là in struct của chính mình bằng {}:
error[E0277]: `Player` doesn't implement `std::fmt::Display` --> src/main.rs:8:20 |8 | println!("{}", p); | -- ^ `Player` cannot be formatted with the default formatter | | | required by this formatting parameter |help: the trait `std::fmt::Display` is not implemented for `Player` = note: in format strings you may be able to use `{:?}` (or {:#?} for pretty-print) insteadThông báo của trình biên dịch luôn bằng tiếng Anh. Nó nói: Player chưa triển khai std::fmt::Display, nên không định dạng được bằng {}. Đây là chương trình gây ra lỗi đó:
struct Player { name: String, score: u32,}
fn main() { let p = Player { name: String::from("Ana"), score: 42 }; println!("{}", p);}Ý nghĩa
Phần tiêu đề “Ý nghĩa”Một trait là một lời hứa rằng kiểu đó làm được một việc gì đó. Display là lời hứa “tôi biết cách in mình ra cho đẹp”. PartialEq là “tôi so sánh được bằng ==”. Send là “tôi được phép chuyển sang luồng khác một cách an toàn”.
Những đoạn code như println!, .sort(), + và thread::spawn đòi hỏi một trong những lời hứa đó. Trình biên dịch chỉ vào giá trị bạn truyền vào (p ở dòng 8) và nói: kiểu này chưa bao giờ hứa điều đó, nên tôi không dùng nó ở đây được.
Hãy nghĩ đến một ổ cắm điện. Ổ cắm chỉ nhận phích cắm có một hình dạng nhất định. Thiết bị của bạn có thể chạy hoàn hảo, nhưng nếu phích cắm không vừa thì không cắm vào được. E0277 là trình biên dịch nói “sai phích cắm”. Bạn sửa bằng cách cho kiểu đó đúng phích cắm: thường là bằng #[derive(...)], đôi khi bằng một khối impl ngắn.
Vì sao Rust làm vậy
Phần tiêu đề “Vì sao Rust làm vậy”Rust không bao giờ đoán. Với kiểu Player của riêng bạn, không có một cách “đúng” duy nhất để in, so sánh hay sắp xếp nó. Hai người chơi có nên được coi là bằng nhau khi tên giống nhau, hay chỉ khi mọi trường đều giống nhau? Rust bắt bạn nói rõ, thay vì lặng lẽ làm một điều bạn không hề muốn.
Việc kiểm tra diễn ra lúc biên dịch. Ở nhiều ngôn ngữ, in một đối tượng mà bạn chưa từng dạy nó cách in sẽ cho ra thứ vô dụng kiểu <Player object at 0x7f...>, còn gửi một giá trị không an toàn với luồng sang luồng khác sẽ gây ra một vụ sập hiếm hoi trên môi trường production. Trong Rust, cả hai đều là lỗi biên dịch mà bạn chỉ cần sửa một lần.
Bạn có thể đọc thêm về cách trait hoạt động ở trang Trait, và về những trait dùng hằng ngày (Debug, Display, PartialEq, Ord, Hash) ở trang Các trait thông dụng.
Cách sửa
Phần tiêu đề “Cách sửa”E0277 có nhiều “gương mặt”. Hãy tìm thông báo khớp với lỗi của bạn ở bên dưới.
Cách sửa 1: In một struct bằng {} hoặc {:?}
Phần tiêu đề “Cách sửa 1: In một struct bằng {} hoặc {:?}”{:?} cần Debug. Bạn gần như không bao giờ phải tự viết nó: hãy thêm #[derive(Debug)]. Dùng nó cho việc debug và ghi log. Lỗi với {:?} trông như thế này:
error[E0277]: `Player` doesn't implement `Debug` = help: the trait `Debug` is not implemented for `Player` = note: add `#[derive(Debug)]` to `Player` or manually `impl Debug for Player`{} cần Display. Rust không derive được nó, vì chỉ bạn mới biết phiên bản “đẹp” nên trông thế nào. Hãy viết một khối impl nhỏ với write!. Dùng nó cho văn bản mà người dùng sẽ thấy.
use std::fmt;
#[derive(Debug)] // có ngay {:?} miễn phístruct Player { name: String, score: u32,}
// Display thì bạn tự viết: nó quyết định {} in ra gì.impl fmt::Display for Player { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { write!(f, "{} ({} điểm)", self.name, self.score) }}
fn main() { let p = Player { name: String::from("Ana"), score: 42 }; println!("{:?}", p); println!("{}", p);}Kết quả:
Player { name: "Ana", score: 42 }Ana (42 điểm)Cách sửa 2: So sánh, sắp xếp, hoặc dùng làm khóa HashMap
Phần tiêu đề “Cách sửa 2: So sánh, sắp xếp, hoặc dùng làm khóa HashMap”.contains() và == cần PartialEq. .sort() cần Ord. Khóa của HashSet và HashMap cần Eq và Hash. Các thông báo trông như thế này:
error[E0277]: can't compare `Point` with `Point` = help: the trait `PartialEq` is not implemented for `Point`
error[E0277]: the trait bound `Priority: Ord` is not satisfied = help: the trait `Ord` is not implemented for `Priority`
error[E0277]: the trait bound `Point: Hash` is not satisfied = help: the trait `Hash` is not implemented for `Point`Cách sửa là derive chúng. Các trait này xây dựng trên nhau, nên bộ đầy đủ thường dùng là PartialEq, Eq, Hash, PartialOrd, Ord:
use std::collections::HashMap;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)]enum Priority { Low, Medium, High,}
fn main() { let mut tasks = vec![Priority::High, Priority::Low, Priority::Medium]; tasks.sort(); // cần Ord println!("{:?}", tasks); println!("Có việc gấp: {}", tasks.contains(&Priority::High)); // cần PartialEq
let mut counts: HashMap<Priority, u32> = HashMap::new(); // khóa cần Eq + Hash for task in &tasks { *counts.entry(*task).or_insert(0) += 1; } println!("Số việc ưu tiên thấp: {}", counts[&Priority::Low]);}Kết quả:
[Low, Medium, High]Có việc gấp: trueSố việc ưu tiên thấp: 1Ord được derive sẽ sắp xếp các biến thể enum theo thứ tự bạn viết chúng, còn struct thì theo từng trường từ trên xuống dưới. Có một điểm cần nói thật: một kiểu có trường f64 không thể derive Eq, Hash hay Ord, vì số thực có NaN, và NaN không bằng chính nó. Để sắp xếp số thực, xem Sắp xếp.
Cách sửa 3: Trộn các kiểu số (cannot add f64 to i32)
Phần tiêu đề “Cách sửa 3: Trộn các kiểu số (cannot add f64 to i32)”error[E0277]: cannot add `f64` to `i32` --> src/main.rs:4:23 |4 | let total = items + price; | ^ no implementation for `i32 + f64` | = help: the trait `Add<f64>` is not implemented for `i32`Rust không bao giờ tự chuyển đổi số giúp bạn, kể cả từ i32 sang f64. Hai vế của +, -, * và / phải cùng kiểu. Hãy tự chuyển đổi một vế bằng as:
fn main() { let items: i32 = 3; let price: f64 = 2.5; let total = items as f64 * price; // chuyển đổi trước, rồi mới nhân println!("Tổng: {total}");}Kết quả:
Tổng: 7.5f64::from(items) cũng được, và không bao giờ làm mất dữ liệu. Đi theo chiều ngược lại (f64 sang i32) sẽ cắt mất phần thập phân, nên hãy nghĩ tới chuyện làm tròn trước. Các trang Ép kiểu và Chuyển đổi kiểu nói chi tiết hơn.
Cách sửa 4: ? couldn’t convert the error
Phần tiêu đề “Cách sửa 4: ? couldn’t convert the error”error[E0277]: `?` couldn't convert the error to `ConfigError` |8 | fn parse_port(text: &str) -> Result<u16, ConfigError> { | ------------------------ expected `ConfigError` because of this9 | let port = text.parse::<u16>()?; | --------------^ the trait `From<ParseIntError>` is not implemented for `ConfigError` | = note: the question mark operation (`?`) implicitly performs a conversion on the error value using the `From` traitKhi ? gặp một lỗi, nó trả lỗi đó về từ hàm của bạn. Nếu kiểu lỗi khác với kiểu mà hàm của bạn đã hứa, ? sẽ cố chuyển đổi nó bằng From. Việc của bạn là viết From đó:
use std::num::ParseIntError;
#[derive(Debug)]enum ConfigError { BadPort(ParseIntError),}
// Dạy `?` cách biến một ParseIntError thành ConfigError.impl From<ParseIntError> for ConfigError { fn from(err: ParseIntError) -> Self { ConfigError::BadPort(err) }}
fn parse_port(text: &str) -> Result<u16, ConfigError> { let port = text.parse::<u16>()?; Ok(port)}
fn main() { println!("{:?}", parse_port("8080")); println!("{:?}", parse_port("tám mươi"));}Kết quả:
Ok(8080)Err(BadPort(ParseIntError { kind: InvalidDigit }))Những lựa chọn tốt khác: chỉ chuyển đổi cho một lời gọi bằng .map_err(ConfigError::BadPort)?, dùng #[from] của crate thiserror, hoặc trả về Result<T, Box<dyn std::error::Error>> trong các chương trình nhỏ. Xem Lỗi tự định nghĩa và anyhow và thiserror.
Cách sửa 5: Rc cannot be sent between threads safely
Phần tiêu đề “Cách sửa 5: Rc cannot be sent between threads safely”error[E0277]: `Rc<String>` cannot be sent between threads safely --> src/main.rs:6:32 |6 | let handle = thread::spawn(move || { | ------------- required by a bound introduced by this call ... = help: within `{closure@src/main.rs:6:32: 6:39}`, the trait `Send` is not implemented for `Rc<String>`Rc đếm số chủ sở hữu bằng một con số bình thường, mà hai luồng có thể cùng lúc thay đổi và làm hỏng nó. Vì vậy Rc không phải Send (thông báo nói: Rc<String> không thể được gửi giữa các luồng một cách an toàn). Hãy dùng Arc (phiên bản “atomic”) thay vào đó. Nó hoạt động y như vậy và an toàn giữa các luồng:
use std::sync::Arc;use std::thread;
fn main() { let config = Arc::new(String::from("debug")); let for_thread = Arc::clone(&config); // một chủ sở hữu thứ hai cho luồng
let handle = thread::spawn(move || { println!("Luồng thấy: {for_thread}"); }); handle.join().unwrap();
println!("main vẫn giữ: {config}");}Kết quả:
Luồng thấy: debugmain vẫn giữ: debugRefCell cũng vậy: giữa các luồng, hãy dùng Arc<Mutex<T>> thay cho Rc<RefCell<T>>. Xem Trạng thái chia sẻ và Đồng thời.
Thường gặp ở đâu
Phần tiêu đề “Thường gặp ở đâu”- Hàm generic. Gọi
fn announce<T: Display>(item: T)với struct của bạn sẽ báo`Song` doesn't implement `std::fmt::Display`kèm “required by a bound inannounce” (bị ràng buộc trongannounceyêu cầu). Cách sửa: triển khai trait mà ràng buộc (trait bound) đòi hỏi. - Test.
assert_eq!(a, b)cần cảPartialEqlẫnDebug(để in các giá trị ra khi test thất bại). Cách sửa:#[derive(Debug, PartialEq)]. .collect()vào sai kiểu.let nums: Vec<i32> = words.iter().collect();báo “a value of typeVec<i32>cannot be built from an iterator over elements of type&String” (không thể tạoVec<i32>từ một iterator gồm các phần tử&String). Cách sửa: chuyển đổi từng phần tử trước, ví dụ.map(|w| w.parse().unwrap()). Xem Iterator.
Lỗi liên quan
Phần tiêu đề “Lỗi liên quan”- E0599: no method found: gọi
.insert()trên mộtHashMapcó khóa không phảiHashđôi khi hiện ra thành E0599 (“trait bounds were not satisfied”). Cách sửa giống vậy: derive các trait. - E0308: mismatched types: bạn đưa một kiểu ở chỗ cần kiểu khác, như một
i32ở chỗ dành chof64. - E0382: borrow of moved value: hay xuất hiện ngay sau khi bạn thêm
ClonehoặcCopylúc đang nghĩ về trait. E0369: binary operation == cannot be applied: phiên bản của toán tử==khi thiếuPartialEq. Cách sửa giống vậy.- Giải thích tất cả lỗi trình biên dịch
Kiểm tra nhanh
Phần tiêu đề “Kiểm tra nhanh”Kiểm tra nhanh
1. Bạn gặp "Order doesn't implement Debug" khi dùng {:?}. Cách sửa nhanh nhất là gì?
2. Vì sao let total = 3_i32 + 2.5_f64; bị lỗi?
3. Code dùng luồng của bạn báo lỗi "Rc<String> cannot be sent between threads safely". Bạn nên dùng gì?
Điểm: 0 / 3
Đọc thêm
Phần tiêu đề “Đọc thêm”- E0277 — Rust error codes index (tiếng Anh): phần giải thích chính thức của lỗi này.
- Traits: Defining Shared Behavior — The Rust Book (tiếng Anh): trait là gì và ràng buộc trait hoạt động thế nào.
- Derivable Traits — The Rust Book, Appendix C (tiếng Anh):
Debug,PartialEq,Eq,Hash,PartialOrdvàOrdmỗi cái cho bạn gì khi được derive. - Recoverable Errors with Result — The Rust Book (tiếng Anh):
?hoạt động thế nào, kể cả phép chuyển đổiFromcủa nó. Send— thư viện chuẩn Rust (tiếng Anh): kiểu nào được chuyển giữa các luồng, và vì saoRcthì không.