Rustのエラー E0369: binary operation cannot be applied to type(演算子が使えない)
E0369 は、==、<、+ のような演算子を、まだそのやり方を知らない型に使った、という意味のエラーです。Rust では、演算子は自分で作った構造体や列挙型に最初から備わっているわけではありません。演算子はそれぞれトレイトから来ていて、そのトレイトを derive するか実装したときに、はじめて型で使えるようになります。たいていは #[derive(...)] を1行足すだけで直りますし、どれを足せばいいかはコンパイラが教えてくれます。このシリーズのほかのエラーは、Rustのコンパイルエラー解説を見てください。
エラーメッセージ
Section titled “エラーメッセージ”error[E0369]: binary operation `==` cannot be applied to type `Point` --> src/main.rs:9:10 |9 | if a == b { // ❌ Point は自分どうしを比べる方法を知らない | - ^^ - 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 { |「二項演算 == は型 Point に適用できない」という意味です。note は「Point には PartialEq の実装が足りないのかもしれない」、help は「Point に #[derive(PartialEq)] を付けてはどうか」と提案しています。
このエラーが出るのは、次のプログラムです。
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 は自分どうしを比べる方法を知らない println!("Same point!"); }}1行目の文言は演算子によって変わりますが、同じエラーです。
binary operation `<` cannot be applied to type `Version`:PartialOrdが足りない(>、<=、>=も同じ)。cannot add `Point` to `Point`(PointにPointを足せない):Addが足りない。「cannot subtract」(引けない)、「cannot multiply」(掛けられない)なども同じ仲間です。cannot add `&str` to `&str`(&strに&strを足せない):2つの文字列スライスを+でつなげようとした。
エラーの意味
Section titled “エラーの意味”演算子の下のラベルは、左右それぞれの型を示しています。ここではどちらも Point です。続く note: はあなたの構造体を指して、PartialEq を実装しなければならない(must implement PartialEq)と言っています。言いかえると、「2つの Point を比べるように頼まれたけれど、Point にとって等しいとはどういうことか、誰も教えてくれていない」ということです。
スポーツの審判が、2つの物を渡されて「どっちが大きい?」と聞かれたところを想像してみてください。数なら答えは明らかです。でも2つのリュックサックだったら、審判にはまずルールが必要です。重さで比べるのか、容量で比べるのか。Rust は、そういう慎重な審判なのです。
Rust がこうする理由
Section titled “Rust がこうする理由”演算子はどれも、メソッド呼び出しが姿を変えたものです。a == b は PartialEq::eq を、a < b は PartialOrd::partial_cmp を、a + b は Add::add を呼びます。数値や String など、標準の型の多くはこれらのトレイトをすでに実装しています。でも新しく作った構造体は、どれも持っていない状態から始まります。
Rust が推測しないのは、推測はときどき間違うからです。2人の User は、ID が同じなら等しいのでしょうか?それともすべてのフィールドが同じときだけでしょうか?Point + Point は座標を足せばいいのでしょうか?そもそも点を足すことに意味はあるのでしょうか?それを決めるのはあなたです。トレイトを derive する(フィールドごとに比べる標準のやり方)か、自分で impl を書きます。これらのトレイトはよく使うトレイトのページで説明しています。
直し方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 した PartialOrd は、フィールドを上から順に比べます。まず 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 にあります。ここで Copy を derive しているので、+ のあとも start と step を使い続けられます。詳しくはよく使うトレイトと高度なトレイトを見てください。
直し方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 が2つなら、右側を借用します(s1 + &s2)。そして format! なら、いつでも使えて何もムーブしません。
fn main() { let first = String::from("こんにちは、"); let second = String::from("Ferris");
let shout = format!("{first}{second}!"); // 両方を借用する let joined = first + &second; // first は joined にムーブされる let short = "やあ、".to_owned() + "カニさん"; // 左が &str なら、先に所有された String にする
println!("{shout}"); println!("{joined}"); println!("{short}"); println!("{second}はまだここにいます");}実行結果:
こんにちは、Ferris!こんにちは、Ferrisやあ、カニさんFerrisはまだここにいます+ は左側の String の所有権を受け取って、その後ろに付け足します。だから、その行のあとでは first が使えません。わかりにくければ、format! を使えば大丈夫です。文字列を見てください。
よく出会う場面
Section titled “よく出会う場面”ジェネリックなコードで比較するとき。 おなじみの 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 の提案どおりに、トレイト境界を付けます。fn largest<T: PartialOrd>(list: &[T]) -> &T です。ジェネリクスを見てください。
列挙型の値を確かめるとき。 if status == Status::Banned は、列挙型に #[derive(PartialEq)] を付けるまでエラーになります。列挙型を変更できない(ほかのクレートのものである)ときは、代わりに matches!(status, Status::Banned) か match を使いましょう。
String を2つつなげるとき。 2つの String で first + second と書いても、E0369 にはなりません。これは E0308(「expected &str, found String」)で、help: consider borrowing here(ここで借用してはどうか)として first + &second が提案されます。同じ仲間のエラーで、直し方も直し方4と同じです。
違う型どうしを比べるとき。 i32 の count と f64 の limit で count == limit と書くと E0308 になり、String の name と char で name == 'F' と書くと E0277(「can’t compare String with char」)になります。目安として、左側の型にその演算子がまったくないときは E0369、演算子はあるけれど右側のその型には対応していないときは E0308 か E0277 になります。直し方:片方を変換します(count as f64 == limit、name == "F")。
関連するエラー
Section titled “関連するエラー”- 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を実装します。- コンパイルエラーの一覧:エラー解説ページの全リストです。
理解度チェック
Section titled “理解度チェック”理解度チェック
1. 構造体 Order に対して if order_a == order_b と書いたら E0369 が出ました。ふつうの直し方はどれですか?
2. struct Version { major: u32, minor: u32 } に #[derive(PartialOrd)] を付けると、2つのバージョンはどう比べられますか?
3. first と second はどちらも &str です。エラーにならずにつなげられる式はどれですか?
スコア: 0 / 3
さらに詳しく
Section titled “さらに詳しく”- E0369 — Rust error codes index(英語):このエラーの公式の説明です。
- 付録C:導出可能なトレイト — The Rust Programming Language 日本語版:
PartialEqとPartialOrdを derive すると何ができるのか、フィールドを比べる順番も含めて説明しています。 - 文字列でUTF-8でエンコードされたテキストを保持する — The Rust Programming Language 日本語版:文字列の
+がなぜ左にString、右に&strを取るのか、いつformat!を使えばいいのかがわかります。 - 演算子のオーバーロード — Rust By Example 日本語版:自分の型に
Addを実装する、実行できる短い例です。 std::ops::Add— 標準ライブラリのドキュメント(英語):AddトレイトとOutput型、違う型どうしの足し算について説明しています。