Bỏ qua để đến nội dung

Lỗi Rust E0404: expected trait, found struct or derive macro (không phải trait)

E0404 nghĩa là: Rust tìm thấy cái tên bạn viết, nhưng nó không phải trait, mà ở chỗ đó chỉ được đặt trait. Những chỗ đó là impl ___ for Type, một ràng buộc như T: ___, và dyn ___ hoặc impl ___. Trường hợp phổ biến nhất làm nhiều người bất ngờ: impl Debug for Point mà không import. Debug có ở trong phạm vi, nhưng dưới dạng macro #[derive(Debug)], không phải dạng trait. Trường hợp phổ biến còn lại là một struct bị dùng như thể nó là trait, điều hay xảy ra khi bạn đã quen với class và kế thừa. Cả hai đều sửa nhanh. (Trang này thuộc loạt bài Giải thích các lỗi trình biên dịch Rust.)

error[E0404]: expected trait, found derive macro `Debug`
--> src/main.rs:6:6
|
6 | impl Debug for Point { // ❌ `Debug` here is the derive macro, not the trait
| ^^^^^ not a trait
|
help: consider importing this trait instead
|
1 + use std::fmt::Debug;
|

Thông báo của trình biên dịch luôn bằng tiếng Anh: “expected trait, found derive macro Debug” nghĩa là “cần một trait, nhưng lại gặp derive macro Debug”. Đây là chương trình gây ra lỗi đó (chú thích được giữ bằng tiếng Anh vì nó xuất hiện nguyên văn trong thông báo lỗi):

struct Point {
x: i32,
y: i32,
}
impl Debug for Point { // ❌ `Debug` here is the derive macro, not the trait
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
write!(f, "Point at ({}, {})", self.x, self.y)
}
}
fn main() {
let p = Point { x: 3, y: 4 };
println!("{:?}", p);
}

Dấu ^^^^^ chỉ vào Debug ở dòng 6, kèm nhãn “not a trait” (không phải trait). Dòng tiêu đề cho biết Rust đã tìm thấy gì thay vào đó: một derive macro tên Debug.

Đó là vì có hai thứ cùng tên Debug. Macro Debug, thứ bạn dùng dưới dạng #[derive(Debug)], nằm trong prelude, nên file nào cũng thấy nó. Trait std::fmt::Debug, thứ bạn cần cho impl Debug for ..., thì không nằm trong prelude. Vì vậy Rust tìm được Debug duy nhất mà nó thấy, nhận ra đó là macro, và dừng lại. Dòng help đưa ra lệnh import để mang trait vào. Hash cũng hoạt động y như vậy: expected trait, found derive macro `Hash` , sửa bằng use std::hash::Hash;.

Khi dòng tiêu đề nói found struct `Animal` (hoặc enum, hoặc type alias), cái tên đó là một kiểu của chính bạn, bị dùng ở chỗ dành cho trait.

Giống như bạn cần “một người có bằng lái xe” mà lại được đưa cho một chiếc ô tô. Ô tô là thứ có thật, và liên quan mật thiết, nhưng công việc cần một người biết lái: một tập hợp khả năng, và đó chính là trait.

Rust tách dữ liệu khỏi hành vi. Một struct hoặc enum mô tả dữ liệu: một giá trị là gì. Một trait mô tả hành vi: một kiểu làm được gì. impl Trait for Type nối hai thứ đó lại: “kiểu này làm được những việc này”. Một ràng buộc như T: Trait nghĩa là “bất kỳ kiểu nào làm được những việc này”.

Không thể dùng struct ở những chỗ đó, vì nó không phải một danh sách khả năng. Và Rust không có kế thừa, nên impl Animal for Dog không thể mang nghĩa “Dog là một loại Animal” khi Animal là struct. Nếu bạn muốn nhiều kiểu cùng chia sẻ hành vi, phần dùng chung chính là một trait. Xem Trait và Generic.

Rust cũng giữ macro và trait trong hai không gian tên (namespace) riêng, đó là lý do Debug có thể vừa là macro vừa là trait. Derive macro nằm trong prelude để #[derive(Debug)] dùng được ở mọi nơi; còn trait chỉ cần được gọi tên khi bạn tự cài đặt nó hoặc dùng nó làm ràng buộc.

Cách sửa 1: Import trait (use std::fmt::Debug;)

Phần tiêu đề “Cách sửa 1: Import trait (use std::fmt::Debug;)”

Chọn cách này khi thông báo nói found derive macro và bạn muốn định dạng riêng, hoặc bạn cần trait trong một ràng buộc như T: Debug. Thêm dòng trong phần help:

use std::fmt::Debug;
struct Point {
x: i32,
y: i32,
}
impl Debug for Point {
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
write!(f, "Điểm tại ({}, {})", self.x, self.y)
}
}
fn print_all<T: Debug>(items: &[T]) {
for item in items {
println!("{:?}", item);
}
}
fn main() {
let p = Point { x: 3, y: 4 };
println!("{:?}", p);
print_all(&[Point { x: 0, y: 0 }, Point { x: -1, y: 7 }]);
}

Kết quả:

Điểm tại (3, 4)
Điểm tại (0, 0)
Điểm tại (-1, 7)
▶ Tự chạy thử

Một cách thay thế phổ biến là use std::fmt; rồi viết impl fmt::Debug for Point cùng fmt::Formatter và fmt::Result. Cả hai đều ổn; hãy chọn một phong cách cho mỗi file.

Cách sửa 2: Dùng #[derive(Debug)] thay vì tự viết impl

Phần tiêu đề “Cách sửa 2: Dùng #[derive(Debug)] thay vì tự viết impl”

Chọn cách này khi bạn không cần định dạng riêng, điều đúng với Debug trong phần lớn trường hợp. Derive macro viết toàn bộ impl Debug for Point giúp bạn, và bạn không cần import gì cả:

#[derive(Debug)] // ✅ derive macro viết `impl Debug for Point` giúp bạn
struct Point {
x: i32,
y: i32,
}
fn main() {
let p = Point { x: 3, y: 4 };
println!("{:?}", p);
println!("{:#?}", p);
println!("khoảng cách từ (0, 0) trên lưới ô vuông: {}", p.x.abs() + p.y.abs());
}

Kết quả:

Point { x: 3, y: 4 }
Point {
x: 3,
y: 4,
}
khoảng cách từ (0, 0) trên lưới ô vuông: 7
▶ Tự chạy thử

Chỉ dùng một trong hai, đừng dùng cả hai: #[derive(Debug)] cộng với impl Debug của riêng bạn sẽ gây ra lỗi “conflicting implementations” (cài đặt xung đột) (E0119, tiếng Anh). {:#?} là dạng in đẹp, nhiều dòng. Xem thêm ở Các trait thông dụng.

Cách sửa 3: Định nghĩa một trait cho hành vi dùng chung

Phần tiêu đề “Cách sửa 3: Định nghĩa một trait cho hành vi dùng chung”

Chọn cách này khi thông báo nói found struct trong một dòng impl X for Y. Có lẽ bạn muốn Dog là “một loại Animal”, như một lớp con (subclass):

error[E0404]: expected trait, found struct `Animal`
--> src/main.rs:7:6
|
7 | impl Animal for Dog { // ❌ `Animal` is a struct, not a trait
| ^^^^^^ not a trait

Chương trình đằng sau lỗi này:

struct Animal {
name: String,
}
struct Dog;
impl Animal for Dog { // ❌ `Animal` is a struct, not a trait
fn name(&self) -> String {
String::from("Rex")
}
}
fn main() {
println!("{}", Dog.name());
}

Trong Rust, phần dùng chung là một trait: danh sách các phương thức mà mỗi kiểu phải cung cấp, cộng với các phương thức mặc định tùy chọn. Dữ liệu vẫn nằm trong từng struct:

trait Animal { // ✅ một trait: những việc mọi con vật đều làm được
fn name(&self) -> String;
fn greet(&self) -> String { // một phương thức mặc định
format!("Chào, mình là {}!", self.name())
}
}
struct Dog;
struct Cat {
name: String, // dữ liệu nằm trong struct
}
impl Animal for Dog {
fn name(&self) -> String {
String::from("Rex")
}
}
impl Animal for Cat {
fn name(&self) -> String {
self.name.clone()
}
}
fn main() {
println!("{}", Dog.greet());
let tom = Cat { name: String::from("Tom") };
println!("{}", tom.greet());
}

Kết quả:

Chào, mình là Rex!
Chào, mình là Tom!
▶ Tự chạy thử

Nếu bạn chỉ muốn thêm phương thức cho một struct, bạn hoàn toàn không cần trait: hãy viết impl Dog { fn name(&self) -> String { ... } }, không có for. Xem Định nghĩa một trait.

Cách sửa 4: Nhận trực tiếp struct, hoặc ràng buộc bằng một trait

Phần tiêu đề “Cách sửa 4: Nhận trực tiếp struct, hoặc ràng buộc bằng một trait”

Chọn cách này khi lỗi chỉ vào một ràng buộc như T: Shape hoặc impl Shape, mà Shape lại là struct của bạn:

error[E0404]: expected trait, found struct `Shape`
--> src/main.rs:5:16
|
5 | fn describe<T: Shape>(item: &T) { // ❌ `Shape` is a struct, not a trait
| ^^^^^ not a trait

Chương trình đằng sau lỗi này:

struct Shape {
sides: u32,
}
fn describe<T: Shape>(item: &T) { // ❌ `Shape` is a struct, not a trait
println!("a shape");
}
fn main() {
describe(&Shape { sides: 3 });
}

Một ràng buộc nghĩa là “bất kỳ kiểu nào cài đặt trait này”. Nếu hàm chỉ bao giờ nhận một Shape, hãy bỏ generic và nhận &Shape. Nếu nó cần dùng được cho nhiều kiểu, hãy đặt hành vi dùng chung vào một trait và ràng buộc bằng trait đó:

struct Shape {
sides: u32,
}
fn describe(item: &Shape) { // ✅ nhận chính struct, không cần generic
println!("một hình có {} cạnh", item.sides);
}
trait Named { // ✅ hoặc một trait, cho “bất kỳ kiểu nào có tên”
fn name(&self) -> String;
}
impl Named for Shape {
fn name(&self) -> String {
match self.sides {
3 => String::from("triangle"),
4 => String::from("square"),
n => format!("{}-gon", n),
}
}
}
fn shout<T: Named>(item: &T) { // ✅ ràng buộc trait cần một trait
println!("{}!", item.name().to_uppercase());
}
fn main() {
let t = Shape { sides: 3 };
describe(&t);
shout(&t);
shout(&Shape { sides: 6 });
}

Kết quả:

một hình có 3 cạnh
TRIANGLE!
6-GON!
▶ Tự chạy thử

Tên hình được giữ bằng tiếng Anh vì chúng được chuyển sang chữ hoa bằng to_uppercase(). Ràng buộc trait được giải thích ở Generic và Trait.

impl Vec<i32> làm kiểu tham số. fn total(x: impl Vec<i32>) cho ra expected trait, found struct `Vec` . impl ở vị trí đó cần một trait. Cách sửa: nhận &[i32] hoặc Vec<i32>, hoặc impl IntoIterator<Item = i32> nếu bạn muốn nhận bất kỳ collection nào.

Box<dyn String>. dyn cũng cần một trait, nên dòng này cho ra expected trait, found struct `String` , và rustc gợi ý ToString. Cách sửa: Box<dyn ToString> nếu ý bạn là “bất cứ thứ gì có thể thành chuỗi”, hoặc chỉ cần String. Xem Trait object.

Alias kiểu cho một trait. type Printable = dyn std::fmt::Display; rồi impl Printable for A cho ra expected trait, found type alias `Printable` với nhãn “type aliases cannot be used as traits” (alias kiểu không thể dùng như trait). Cách sửa: viết tên thật của trait trong impl.

Kiểm tra nhanh

1. impl Debug for Point báo “expected trait, found derive macro Debug”. Cách sửa đơn giản nhất là gì nếu bạn muốn định dạng riêng?

2. Bạn có struct Animal và viết impl Animal for Dog. Rust muốn gì thay vào đó?

3. Thứ nào dưới đây có thể đặt sau T: trong fn show<T: ___>(x: T)?

Điểm: 0 / 3