Rust 错误 E0369:binary operation cannot be applied to type(不支持该二元运算)
E0369 的意思是:你对一个类型用了 ==、< 或 + 这样的运算符,可这个类型还不知道怎么做这件事。在 Rust 里,你自己写的结构体和枚举并不自带运算符。每个运算符都来自一个 trait,只有 derive 或实现了那个 trait,你的类型才能用它。修复办法通常就是加一行 #[derive(...)],而且编译器会直接告诉你该加哪个。这个系列里的其他错误,见 Rust 编译错误详解。
error[E0369]: binary operation `==` cannot be applied to type `Point` --> src/main.rs:9:10 |9 | if a == b { // ❌ Point doesn't know how to compare itself | - ^^ - Point | | | Point |note: an implementation of `PartialEq` might be missing for `Point` --> src/main.rs:1:1 |1 | struct Point { | ^^^^^^^^^^^^ must implement `PartialEq`help: consider annotating `Point` with `#[derive(PartialEq)]` |1 + #[derive(PartialEq)]2 | struct Point { |下面这段程序会产生这个错误:
struct Point { x: i32, y: i32,}
fn main() { let a = Point { x: 1, y: 2 }; let b = Point { x: 1, y: 2 }; if a == b { // ❌ Point doesn't know how to compare itself println!("Same point!"); }}(代码里的注释保留英文,因为 rustc 会把这一行原样打印在错误信息里,意思是“Point 不知道怎么和自己比较”。)
第一行会随运算符变化,但都是同一个错误:
binary operation `<` cannot be applied to type `Version`:缺少PartialOrd(>、<=、>=也一样)。cannot add `Point` to `Point`:缺少Add。你还会看到 “cannot subtract”(不能相减)、“cannot multiply”(不能相乘)等等。cannot add `&str` to `&str`:你想用+拼接两个字符串 slice。
这个错误是什么意思
Section titled “这个错误是什么意思”运算符下面的标注显示两边的类型,这里是 Point 和 Point。接着 note: 指向你的结构体,说它 must implement PartialEq(必须实现 PartialEq)。换句话说:“你让我比较两个 Point,可没人告诉过我,对 Point 来说‘相等’是什么意思。”
想象一位裁判,别人递给他两样东西,问他“哪个更大?”。如果是数字,答案一目了然。如果是两个背包,裁判得先有个规则:按重量?按容量?Rust 就是这样一位谨慎的裁判。
Rust 为什么要这样做
Section titled “Rust 为什么要这样做”每个运算符其实都是一次披着外衣的方法调用。a == b 调用的是 PartialEq::eq,a < b 调用的是 PartialOrd::partial_cmp,a + b 调用的是 Add::add。数字、String 和许多标准库类型早就实现了这些 trait,而你新写的结构体一个都没有。
Rust 不会去猜,因为猜总有猜错的时候。两个 User 是 ID 相同就算相等,还是每个字段都相同才算?Point + Point 是把坐标相加,还是说两个点相加根本没有意义?这些由你来决定:derive 这个 trait(默认逐个字段比较),或者自己写 impl。这些 trait 在 常用 trait 一页里有讲解。
修复 1:用 #[derive(PartialEq)] 支持 == 和 !=
Section titled “修复 1:用 #[derive(PartialEq)] 支持 == 和 !=”如果“相等”就是“所有字段都相等”,用 == 和 != 时就选这个办法。大多数时候这都是正确的修复,也正是 rustc 给出的建议。再加上 Debug,就能把结构体打印出来:
#[derive(Debug, PartialEq)]struct Point { x: i32, y: i32,}
fn main() { let a = Point { x: 1, y: 2 }; let b = Point { x: 1, y: 2 }; let c = Point { x: 5, y: 0 }; if a == b { println!("是同一个点!"); } println!("{:?} != {:?}: {}", a, c, a != c);}输出:
是同一个点!Point { x: 1, y: 2 } != Point { x: 5, y: 0 }: true每个字段本身也必须能比较。如果某个字段的类型没有 PartialEq,也给那个类型 derive 上。枚举也一样:给 enum Status 加上 #[derive(PartialEq)],status == Status::Banned 就能编译了。
修复 2:用 #[derive(PartialOrd)] 支持 < 和 >
Section titled “修复 2:用 #[derive(PartialOrd)] 支持 < 和 >”如果你的类型有一个自然的顺序,就选这个办法。PartialOrd 依赖 PartialEq,所以 rustc 会建议 #[derive(PartialEq, PartialOrd)]。derive 出来的版本会从上到下比较字段:先比 major,只有 major 相等时才比 minor。所以结构体里字段的顺序很重要:
#[derive(Debug, PartialEq, PartialOrd)]struct Version { major: u32, minor: u32,}
fn main() { let installed = Version { major: 1, minor: 4 }; let required = Version { major: 1, minor: 7 }; if installed < required { println!("请从 {:?} 升级", installed); }
let next = Version { major: 2, minor: 0 }; println!("2.0 比 1.7 新:{}", next > required);}输出:
请从 Version { major: 1, minor: 4 } 升级2.0 比 1.7 新:true如果想对你的类型组成的 Vec 调用 .sort(),还需要 Eq 和 Ord。见 排序。
修复 3:用 impl std::ops::Add 支持 +
Section titled “修复 3:用 impl std::ops::Add 支持 +”如果把两个你的类型的值相加有明确的含义,比如坐标相加或金额相加,就选这个办法。数学运算符没有 derive,所以要写一个简短的 impl。Output 是 + 返回的类型:
use std::ops::Add;
#[derive(Debug, Clone, Copy, PartialEq)]struct Point { x: i32, y: i32,}
impl Add for Point { type Output = Point;
fn add(self, other: Point) -> Point { Point { x: self.x + other.x, y: self.y + other.y } }}
fn main() { let start = Point { x: 1, y: 2 }; let step = Point { x: 3, y: 4 }; let end = start + step; println!("{:?}", end); println!("{}", end == Point { x: 4, y: 6 });}输出:
Point { x: 4, y: 6 }trueSub、Mul、Div 和 Neg 的用法相同,都在 std::ops 里。这里 derive 了 Copy,所以做完 + 之后 start 和 step 还能继续用。更多内容见 常用 trait 和 高级 trait。
修复 4:用 + 或 format! 拼接字符串
Section titled “修复 4:用 + 或 format! 拼接字符串”如果错误信息是 cannot add `&str` to `&str`,就选这个办法。字符串上的 + 要求左边是拥有所有权的 String,右边是 &str。rustc 的 help 用它自己的话说了这一点:“create an owned String from a string reference”(从字符串引用创建一个拥有所有权的 String),也就是 first.to_owned() + second。如果两边都是 String,就借用右边那个:s1 + &s2。而 format! 永远管用,也不会移动任何值:
fn main() { let first = String::from("Hello, "); let second = String::from("Ferris");
let shout = format!("{first}{second}!"); // 两个都只是借用 let joined = first + &second; // first 被移动进了 joined let short = "Hi, ".to_owned() + "crab"; // 左边是 &str:先把它变成 String
println!("{shout}"); println!("{joined}"); println!("{short}"); println!("{second} 还能用");}输出:
Hello, Ferris!Hello, FerrisHi, crabFerris 还能用+ 会拿走左边那个 String 的所有权,再把内容追加到它后面,所以那一行之后就不能再用 first 了。如果觉得绕,直接用 format! 就好。见 字符串。
在泛型代码里比较。 经典的 largest 函数会报 binary operation `>` cannot be applied to type `&T`,因为 T 可以是任何类型,包括没有顺序的类型:
fn largest<T>(list: &[T]) -> &T { let mut largest = &list[0]; for item in list { if item > largest { // ❌ T 可能根本不能比较 largest = item; } } largest}修复:照 rustc 的建议加上 trait 约束:fn largest<T: PartialOrd>(list: &[T]) -> &T。见 泛型。
检查枚举的值。 在枚举加上 #[derive(PartialEq)] 之前,if status == Status::Banned 都会报错。如果你改不了这个枚举(它来自别的 crate),就改用 matches!(status, Status::Banned) 或者 match。
拼接两个 String。 两个 String 写 first + second 不是 E0369,而是 E0308(“expected &str, found String”,期望 &str,却得到 String),并附带 help: consider borrowing here,也就是 first + &second。同一类问题,修复方法和修复 4 一样。
比较两个不同的类型。 用 count == limit 比较一个 i32 和一个 f64 会得到 E0308;用 name == 'F' 比较一个 String 和一个 char 会得到 E0277(“can’t compare String with char”)。经验法则:左边的类型完全没有这个运算符时,报 E0369;有这个运算符、只是不支持右边这个类型时,报 E0308 或 E0277。修复:把一边转换一下(count as f64 == limit、name == "F")。
- E0277:trait bound not satisfied:左边的类型有这个运算符,但不支持右边这个类型(
i32 + f64、String == char)。 - E0308:mismatched types:
String + String,或者比较两个不同的结构体时,会得到这个错误。 - E0599:no method found:类型缺少
Ord或PartialEq时,调用.sort()或.contains()也会以同样的方式失败。 E0368: binary assignment operation += cannot be applied:这个错误的+=版本。实现std::ops里的AddAssign即可。- 所有编译错误:全部错误页面的列表。
小测验
1. 你给结构体 Order 写了 if order_a == order_b,结果报 E0369。通常怎么修复?
2. 给 struct Version { major: u32, minor: u32 } 加上 #[derive(PartialOrd)] 后,两个版本是怎么比较的?
3. first 和 second 都是 &str。哪个表达式能拼接它们而不报错?
得分: 0 / 3
- E0369 — Rust error codes index(英文):这个错误的官方说明。
- 附录 C:可派生的 trait —《Rust 程序设计语言》:derive
PartialEq和PartialOrd能得到什么,包括逐个字段比较的顺序。 - 使用字符串储存 UTF-8 编码的文本 —《Rust 程序设计语言》:为什么字符串的
+左边要String、右边要&str,以及什么时候该用format!。 - 运算符重载 —《通过例子学 Rust》:一个为自己的类型实现
Add的简短可运行示例。 std::ops::Add— 标准库文档(英文):Addtrait、它的Output类型,以及不同类型的值相加。