跳转到内容

Rust 错误 E0404:expected trait, found struct or derive macro(需要 trait 却给了别的)

E0404 的意思是:Rust 找到了你写的这个名字,但它不是 trait,而这个位置只能放 trait。 这样的位置有:impl ___ for Type、T: ___ 这样的约束,以及 dyn ___ 或 impl ___。最常见的情况会让很多人吃惊:没有导入就写了 impl Debug for Point。Debug 确实在作用域里,但那是 #[derive(Debug)] 用的宏,而不是 trait。另一种常见情况是把结构体当成 trait 来用,习惯了类和继承的人经常这样写。两种都很好修。(本页属于 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;
|

第一行的意思是“这里需要 trait,找到的却是 derive 宏 Debug”。help: 建议改为导入这个 trait。

产生这个错误的程序是这样的:

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);
}

^^^^^ 指着第 6 行的 Debug,标注是“not a trait”(不是 trait)。第一行说明了 Rust 实际找到的是什么:一个叫 Debug 的 derive 宏。

这是因为有两样东西都叫 Debug。宏 Debug(也就是你写的 #[derive(Debug)])在 prelude 里,所以每个文件都能看到它。trait std::fmt::Debug(写 impl Debug for ... 时需要的那个)不在 prelude 里。于是 Rust 找到了它唯一能看到的 Debug,发现那是个宏,就停下了。help 那一行给出了把 trait 引入的 use 语句。Hash 也是同样的情况:expected trait, found derive macro `Hash` ,用 use std::hash::Hash; 修复。

如果第一行写的是 found struct `Animal` (或者 enum、type alias),说明这个名字是你自己定义的类型,被用在了应该放 trait 的地方。

就像你要找“一个有驾照的司机”,别人却递给你一辆车。车是实实在在的东西,也和开车密切相关,但这份工作要的是会开车的人:一组能力,而 trait 正是一组能力。

Rust 把数据和行为分开。结构体或枚举描述数据:一个值是什么。trait 描述行为:一个类型能做什么。impl Trait for Type 把两者连起来:“这个类型能做这些事”。T: Trait 这样的约束表示“任何能做这些事的类型”。

结构体不能用在这些位置,因为它不是一组能力。而且 Rust 没有继承,所以当 Animal 是结构体时,impl Animal for Dog 不可能表示“Dog 是一种 Animal”。如果想让多个类型共享某种行为,共享的那部分就应该是 trait。参见trait和泛型。

Rust 还把宏和 trait 放在不同的命名空间里,所以 Debug 既可以是宏,也可以是 trait。derive 宏在 prelude 里,所以 #[derive(Debug)] 在哪儿都能用;只有当你自己实现这个 trait 或者用它做约束时,才需要写出 trait 的名字。

修复 1:导入 trait(use std::fmt::Debug;)

Section titled “修复 1:导入 trait(use std::fmt::Debug;)”

当错误信息里写着 found derive macro,而你想要自己的输出格式,或者需要在 T: Debug 这样的约束里用到这个 trait 时,选这个方法。加上 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, "Point at ({}, {})", 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 }]);
}

输出:

Point at (3, 4)
Point at (0, 0)
Point at (-1, 7)
▶ 动手试一试

另一种常见写法是 use std::fmt;,然后写 impl fmt::Debug for Point,配合 fmt::Formatter 和 fmt::Result。两种都可以,同一个文件里统一用一种就好。

修复 2:用 #[derive(Debug)] 代替手写实现

Section titled “修复 2:用 #[derive(Debug)] 代替手写实现”

当你不需要自定义格式时选这个方法,对 Debug 来说大多数时候都是这样。derive 宏会替你写好整个 impl Debug for Point,而且不需要任何导入:

#[derive(Debug)] // ✅ derive 宏替你写好 `impl Debug for Point`
struct Point {
x: i32,
y: i32,
}
fn main() {
let p = Point { x: 3, y: 4 };
println!("{:?}", p);
println!("{:#?}", p);
println!("distance from (0, 0) on a grid: {}", p.x.abs() + p.y.abs());
}

输出:

Point { x: 3, y: 4 }
Point {
x: 3,
y: 4,
}
distance from (0, 0) on a grid: 7
▶ 动手试一试

两者只能选一个:同时写 #[derive(Debug)] 和你自己的 impl Debug,会得到“conflicting implementations”(实现冲突)错误(E0119(英文))。{:#?} 是美化后的多行格式。详见常用 trait。

修复 3:为共享的行为定义一个 trait

Section titled “修复 3:为共享的行为定义一个 trait”

当错误信息在 impl X for Y 这一行写着 found struct 时选这个方法。你大概是想让 Dog 成为“一种 Animal”,就像子类那样:

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

背后的程序:

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());
}

在 Rust 里,共享的部分是 trait:一组每个类型都必须提供的方法,再加上可选的默认方法。数据则留在各自的结构体里:

trait Animal { // ✅ trait:每种动物都能做的事
fn name(&self) -> String;
fn greet(&self) -> String { // 默认方法
format!("你好,我是 {}!", self.name())
}
}
struct Dog;
struct Cat {
name: String, // 数据放在结构体里
}
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());
}

输出:

你好,我是 Rex!
你好,我是 Tom!
▶ 动手试一试

如果你只是想给某一个结构体加方法,根本不需要 trait:写 impl Dog { fn name(&self) -> String { ... } } 就行,不带 for。参见定义 trait。

修复 4:直接接收结构体,或者用 trait 做约束

Section titled “修复 4:直接接收结构体,或者用 trait 做约束”

当错误指向 T: Shape 或 impl Shape 这样的约束,而 Shape 是你的结构体时,选这个方法:

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

背后的程序:

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 });
}

约束的意思是“任何实现了这个 trait 的类型”。如果函数永远只接收 Shape,就去掉泛型,直接接收 &Shape。如果它应该适用于多种类型,就把共享的行为放进一个 trait,再用这个 trait 做约束:

struct Shape {
sides: u32,
}
fn describe(item: &Shape) { // ✅ 直接接收结构体,不需要泛型
println!("a shape with {} sides", item.sides);
}
trait Named { // ✅ 或者用 trait 表示“任何有名字的类型”
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) { // ✅ trait 约束需要的是 trait
println!("{}!", item.name().to_uppercase());
}
fn main() {
let t = Shape { sides: 3 };
describe(&t);
shout(&t);
shout(&Shape { sides: 6 });
}

输出:

a shape with 3 sides
TRIANGLE!
6-GON!
▶ 动手试一试

trait 约束在泛型和trait两页里有讲解。

把 impl Vec<i32> 当参数类型。 fn total(x: impl Vec<i32>) 会报 expected trait, found struct `Vec` 。这个位置的 impl 后面需要 trait。修复:接收 &[i32] 或 Vec<i32>;如果想接受任何集合,就用 impl IntoIterator<Item = i32>。

Box<dyn String>。 dyn 后面也需要 trait,所以这会报 expected trait, found struct `String` ,rustc 会建议用 ToString。修复:如果你的意思是“任何能变成字符串的东西”,写 Box<dyn ToString>;否则直接用 String。参见trait 对象。

给 trait 起的类型别名。 先写 type Printable = dyn std::fmt::Display;,再写 impl Printable for A,会报 expected trait, found type alias `Printable` ,标注是“type aliases cannot be used as traits”(类型别名不能当 trait 用)。修复:在 impl 里写出 trait 的真实名字。

小测验

1. impl Debug for Point 报了“expected trait, found derive macro Debug”。如果你想要自定义格式,最简单的修法是什么?

2. 你有一个 struct Animal,然后写了 impl Animal for Dog。Rust 希望你怎么写?

3. 在 fn show<T: ___>(x: T) 里,T: 后面可以放下面哪一个?

得分: 0 / 3