Rustのエラー E0368: binary assignment operation += cannot be applied to type(+= が使えない)
E0368 は、+=、-=、*= のような複合代入演算子を、まだそれに対応していない型に使った、という意味のエラーです。E0369 の += 版のいとこのようなものです。自分で作った構造体は、+= を自動では使えるようになりません。+ にすでに対応していても、です。たいていは短い impl AddAssign ブロックで直りますし、参照の先にある数値なら * を1つ付けるだけです。このシリーズのほかのエラーは、Rustのコンパイルエラー解説を見てください。
エラーメッセージ
Section titled “エラーメッセージ”error[E0368]: binary assignment operation `+=` cannot be applied to type `Money` --> src/main.rs:8:5 |8 | wallet += tip; // ❌ Money は += のやり方を知らない | ------^^^^^^^ | | | cannot use `+=` on type `Money` |note: an implementation of `AddAssign` might be missing for `Money` --> src/main.rs:1:1 |1 | struct Money { | ^^^^^^^^^^^^ must implement `AddAssign`「二項代入演算 += は型 Money に適用できない」という意味です。note は「Money には AddAssign の実装が足りないのかもしれない」と言っています。
このエラーが出るのは、次のプログラムです。
struct Money { cents: i64,}
fn main() { let mut wallet = Money { cents: 500 }; let tip = Money { cents: 150 }; wallet += tip; // ❌ Money は += のやり方を知らない println!("{}", wallet.cents);}1行目の演算子は変わることがありますが、同じエラーです。p -= step なら SubAssign が求められ、p *= 2 なら MulAssign<{integer}> が求められます。これは「整数で掛け算して代入する」という意味です。
エラーの意味
Section titled “エラーの意味”左側の下にあるラベルが、何がいけなかったのかを教えてくれます。「cannot use += on type Money」(型 Money には += を使えない)です。続く note: はあなたの構造体を指して、AddAssign を実装しなければならない(must implement AddAssign)と言っています。これが += の裏にあるトレイトです。
貯金箱を思い浮かべてみてください。硬貨を1枚入れるのは、はっきりした動作です。でも「Money」と書かれた箱を渡されて、「この別の箱を足しておいて」と言われたら、どうすればいいのか知る必要があります。セントを足し合わせるのか、箱を積み重ねるのか。Rust は、そのルールを一度書いておいてほしいのです。
Rust がこうする理由
Section titled “Rust がこうする理由”複合代入演算子はどれも、メソッド呼び出しが姿を変えたものです。a += b は AddAssign::add_assign(&mut a, b) を呼び、a -= b は SubAssign::sub_assign を呼ぶ、というように続きます。これらのトレイトはすべて std::ops にあります。数値はもう実装していますが、新しく作った構造体はどれも持っていない状態から始まります。しかも、算術演算子には #[derive] がありません。
多くの人が驚くのがここです。Add と AddAssign は別々のトレイトなのです。Point が Add を実装していても、p += q はやはり E0368 になります。理由は、この2つが違う仕事をするからです。a + b は2つの値を受け取って新しい値を返します。a += b は &mut self を通して a をその場で書き換え、何も返しません。その場で書き換えるほうが、ずっと安く済むことがあります。String の += は、新しい文字列を作るのではなく、今あるバッファの後ろに付け足します。Rust はそれぞれの型に選ばせるので、+= を黙って + に置き換えたりはしません。
演算子のトレイトは、よく使うトレイトと高度なトレイトで説明しています。
直し方1:AddAssign(とその仲間)を実装する
Section titled “直し方1:AddAssign(とその仲間)を実装する”自分の型で += にはっきりした意味があるとき(お金を足す、位置を動かすなど)は、これを選びます。self を書き換えるメソッドを1つ書くだけです。必要なら、SubAssign や MulAssign なども同じように追加します。
use std::ops::{AddAssign, SubAssign};
#[derive(Debug)]struct Money { cents: i64,}
impl AddAssign for Money { fn add_assign(&mut self, other: Money) { self.cents += other.cents; }}
impl SubAssign for Money { fn sub_assign(&mut self, other: Money) { self.cents -= other.cents; }}
fn main() { let mut wallet = Money { cents: 500 }; wallet += Money { cents: 150 }; // チップをもらった wallet -= Money { cents: 200 }; // お昼ごはんを買った println!("{:?}", wallet);}実行結果:
Money { cents: 450 }&mut self を受け取り、戻り値の型がないことに注目してください。Add との違いはそれだけです。中の self.cents += other.cents が動くのは、i64 がすでに AddAssign を持っているからです。
直し方2:Add があるなら、a = a + b と書くか、それを使い回す
Section titled “直し方2:Add があるなら、a = a + b と書くか、それを使い回す”型がすでに Add を実装しているとき(E0369 を直したあとなど)は、これを選びます。型が Copy なら、pos = pos + step と書くだけで済みます。これに必要なのは Add だけです。あるいは、自分の Add を呼ぶ1行で AddAssign を実装すれば、+= も使えるようになります。
use std::ops::{Add, AddAssign};
#[derive(Debug, Clone, Copy)]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 } }}
impl AddAssign for Point { fn add_assign(&mut self, other: Point) { *self = *self + other; // Add を使い回す }}
fn main() { let mut pos = Point { x: 0, y: 0 }; let step = Point { x: 1, y: 2 }; pos = pos + step; // Add だけで足りる pos += step; // AddAssign が必要 println!("{:?}", pos);}実行結果:
Point { x: 2, y: 4 }*self = *self + other は、今の値をコピーして取り出し、足して、その結果を書き戻します。これが動くのは Point が Copy だからです。ヒープのデータを持つ型(Vec のフィールドがあるなど)では、代わりに直し方1のような、その場で書き換えるやり方で書きましょう。
直し方3:文字列には s += &t か push_str
Section titled “直し方3:文字列には s += &t か push_str”左側が文字列のときは、これを選びます。+= は所有された String に使えて、右側は &str でなければなりません。よくつまずくケースが2つあります。
cannot be applied to type `&str`:左側が"Hello, "のような文字列スライスになっています。&strは読み取り専用のビューなので、伸ばすことができません。先にStringにしましょう。tもStringのときのs += tは E0368 ではありません。これは E0308 の「expected&str, foundString」で、help としてs += &tが提案されます。右側を借用するだけです。
fn main() { let mut greeting = String::from("こんにちは、"); // 所有しているので伸ばせる let name = String::from("Ferris");
greeting += &name; // 右側を借用する greeting.push_str("!"); // メソッドで書いても同じこと
let mut tag = "crab".to_string(); // 左が &str なら String にする tag += " #1";
println!("{greeting}"); println!("{tag}"); println!("{name}はまだ使えます");}実行結果:
こんにちは、Ferris!crab #1Ferrisはまだ使えます右側は借用されるだけなので、そのあとも name はあなたのものです。+= と push_str はまったく同じことをします。読みやすいほうを使いましょう。文字列を見てください。
直し方4:ジェネリックなコードには T: AddAssign 境界を付ける
Section titled “直し方4:ジェネリックなコードには T: AddAssign 境界を付ける”エラーが cannot be applied to type `T` のときは、これを選びます。ジェネリックな関数の中では、T はどんな型でもありえて、+= を持たない型かもしれません。rustc の help が、何を付ければいいかをそのまま教えてくれます。consider further restricting type parameter `T` with trait `AddAssign`(型パラメータ T をトレイト AddAssign でさらに制限してはどうか)です。
use std::ops::AddAssign;
fn total<T>(items: &[T]) -> Twhere T: Copy + Default + AddAssign,{ let mut sum = T::default(); for &item in items { sum += item; } sum}
fn main() { println!("{}", total(&[1, 2, 3])); println!("{}", total(&[0.5, 0.25]));}実行結果:
60.75これで total は、コピーできて、初期値があって、+= に対応している型なら何にでも使えます。整数、浮動小数点数、そしてこれらのトレイトを実装すれば自分の Money 型にもです。ただの数値なら、items.iter().sum() の1行で同じことができます。ジェネリクスを見てください。
よく出会う場面
Section titled “よく出会う場面”HashMap で数を数えるとき。 let count = counts.entry(word).or_insert(0); のあとに count += 1; と書くと、cannot be applied to type `&mut i32` になります。count は数値そのものではなく参照だからです。rustc の help は「+= can be used on i32 if you dereference the left-hand side」(左側を参照外しすれば、i32 に += を使える)と言っています。直し方:*count += 1; と書きます。HashMapを見てください。
構造体を数で拡大するとき。 p *= 2 には MulAssign<{integer}> が必要です。右側が別の Point ではなく数値だからです。直し方:その右側の型に対してトレイトを実装します。
impl std::ops::MulAssign<i32> for Point { fn mul_assign(&mut self, factor: i32) { self.x *= factor; self.y *= factor; }}ループで合計を積み上げるとき。 price が自分の Money 型のときの total += item.price です。直し方1で動くようになります。prices.iter().copied().sum() と書きたいなら、代わりに Sum トレイトが必要です。それがないと E0277(「a value of type Money cannot be made by summing an iterator」、イテレータを合計しても Money 型の値は作れない)になります。
関連するエラー
Section titled “関連するエラー”- E0369: binary operation cannot be applied:
+、==、<など、代入しない演算子で起こる同じ問題です。 - E0308: mismatched types:2つの
Stringでs += tと書いたときに出るエラーです(借用しましょう:s += &t)。 - E0277: trait bound not satisfied:左側の型に
+=はあるけれど、右側のその型には対応していない。totalが整数のときのtotal += 2.5で出るcannot add-assign `{float}` to `{integer}`などです。 - E0384: cannot assign twice:
mutなしで宣言した変数にx += 1と書いた。演算子は問題なく、変数が可変でないだけです。 - コンパイルエラーの一覧:エラー解説ページの全リストです。
理解度チェック
Section titled “理解度チェック”理解度チェック
1. Point は Add を実装しているのに、p += q で E0368 が出ます。なぜですか?
2. count は map.entry(word).or_insert(0) から得たものです。コンパイルできる行はどれですか?
3. s と t はどちらも String です。s の後ろに t を付け足すにはどう書きますか?
スコア: 0 / 3
さらに詳しく
Section titled “さらに詳しく”- E0368 — Rust error codes index(英語):このエラーの公式の説明です。
std::ops::AddAssign— 標準ライブラリのドキュメント(英語):+=の裏にあるトレイトで、例も載っています。SubAssign、MulAssignなどはstd::ops(英語)に一覧があります。- 高度なトレイト — The Rust Programming Language 日本語版:
std::opsを使った演算子のオーバーロードと、Rhsのようなデフォルト型引数について説明しています。 - 文字列でUTF-8でエンコードされたテキストを保持する — The Rust Programming Language 日本語版:文字列が
push_strで伸びる理由と、+の右側が&strである理由がわかります。 - 演算子のオーバーロード — Rust By Example 日本語版:自分の型に演算子のトレイトを実装する、実行できる短い例です。