Erreur Rust E0368 : binary assignment operation += cannot be applied to type
E0368 veut dire que vous avez utilisé un opérateur composé comme +=, -= ou *= sur un type qui ne le prend pas encore en charge. C’est la cousine « += » de E0369. Vos propres structs n’obtiennent pas += automatiquement, même quand elles savent déjà faire +. La correction est le plus souvent un petit bloc impl AddAssign, et sur des nombres derrière une référence, il suffit d’ajouter un *. Pour les autres erreurs de cette série, consultez les erreurs du compilateur Rust.
Le message d’erreur
Section intitulée « Le message d’erreur »error[E0368]: binary assignment operation `+=` cannot be applied to type `Money` --> src/main.rs:8:5 |8 | wallet += tip; // ❌ Money ne sait pas faire += | ------^^^^^^^ | | | 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`Le compilateur dit : « l’opération d’affectation += ne peut pas être appliquée au type Money », et il ajoute qu’il manque peut-être une implémentation de AddAssign.
Voici le programme qui la provoque :
struct Money { cents: i64,}
fn main() { let mut wallet = Money { cents: 500 }; let tip = Money { cents: 150 }; wallet += tip; // ❌ Money ne sait pas faire += println!("{}", wallet.cents);}L’opérateur de la première ligne change, mais c’est la même erreur. p -= step demande SubAssign, et p *= 2 demande MulAssign<{integer}>, ce qui veut dire « multiplier-affecter par un nombre entier ».
Ce que ça veut dire
Section intitulée « Ce que ça veut dire »L’étiquette sous le côté gauche dit ce qui ne va pas : « cannot use += on type Money » (impossible d’utiliser += sur le type Money). La note: montre ensuite votre struct et dit qu’elle doit implémenter AddAssign. C’est le trait (une capacité qu’un type déclare avoir) qui se cache derrière +=.
Pensez à une tirelire. Y glisser une pièce, c’est une action claire. Mais si quelqu’un vous tend une boîte marquée « Argent » et vous dit « ajoute cette autre boîte dedans », il vous faut savoir comment : additionner les centimes ? empiler les boîtes ? Rust vous demande d’écrire cette règle une fois pour toutes.
Pourquoi Rust fait ça
Section intitulée « Pourquoi Rust fait ça »Chaque opérateur composé est un appel de méthode déguisé. a += b appelle AddAssign::add_assign(&mut a, b), a -= b appelle SubAssign::sub_assign, et ainsi de suite. Tous ces traits se trouvent dans std::ops. Les nombres les implémentent déjà. Votre nouvelle struct n’en a aucun au départ, et il n’existe pas de #[derive] pour les opérateurs mathématiques.
Voici ce qui surprend beaucoup de monde : Add et AddAssign sont deux traits distincts. Même si Point implémente déjà Add, p += q donne toujours E0368. La raison, c’est qu’ils ne font pas le même travail. a + b prend deux valeurs et en renvoie une nouvelle. a += b modifie a sur place à travers &mut self et ne renvoie rien. Modifier sur place peut coûter beaucoup moins cher : le += de String ajoute le texte au tampon existant au lieu de construire une nouvelle chaîne. Rust laisse chaque type choisir, et il ne transforme donc jamais += en + derrière votre dos.
Les traits des opérateurs sont expliqués dans Les traits courants et Les traits avancés.
Comment corriger
Section intitulée « Comment corriger »Solution 1 : implémenter AddAssign (et ses cousins)
Section intitulée « Solution 1 : implémenter AddAssign (et ses cousins) »Choisissez cette solution quand += a un sens clair pour votre type, comme ajouter de l’argent ou déplacer une position. Vous écrivez une seule méthode, qui modifie self. Ajoutez SubAssign, MulAssign et les autres de la même façon quand vous en avez besoin :
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 }; // un pourboire wallet -= Money { cents: 200 }; // le déjeuner println!("{:?}", wallet);}Sortie :
Money { cents: 450 }Remarquez le &mut self et l’absence de type de retour : c’est toute la différence avec Add. À l’intérieur, self.cents += other.cents fonctionne parce que i64 a déjà AddAssign.
Solution 2 : vous avez déjà Add ? Écrivez a = a + b, ou réutilisez-le
Section intitulée « Solution 2 : vous avez déjà Add ? Écrivez a = a + b, ou réutilisez-le »Choisissez cette solution quand votre type implémente déjà Add (peut-être après avoir corrigé E0369). Si le type est Copy, vous pouvez tout simplement écrire pos = pos + step, qui n’a besoin que de Add. Ou bien implémentez AddAssign en une ligne en appelant votre Add, pour que += marche aussi :
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; // on réutilise Add }}
fn main() { let mut pos = Point { x: 0, y: 0 }; let step = Point { x: 1, y: 2 }; pos = pos + step; // n'a besoin que de Add pos += step; // a besoin de AddAssign println!("{:?}", pos);}Sortie :
Point { x: 2, y: 4 }*self = *self + other copie la valeur actuelle, fait l’addition et réécrit le résultat. Ça ne marche que parce que Point est Copy. Pour un type qui possède des données sur le tas (comme un champ Vec), écrivez plutôt la version sur place de la solution 1.
Solution 3 : les chaînes, avec s += &t ou push_str
Section intitulée « Solution 3 : les chaînes, avec s += &t ou push_str »Choisissez cette solution quand le côté gauche est une chaîne. += marche sur une String possédée, et le côté droit doit être un &str. Deux cas font souvent trébucher :
cannot be applied to type `&str`: le côté gauche est une tranche de chaîne comme"Bonjour, ". Un&strest une vue en lecture seule, il ne peut pas grandir. Transformez-le d’abord enString.s += toùtest aussi uneString, ce n’est pas E0368. C’est E0308, « expected&str, foundString», avec l’aides += &t. Empruntez simplement le côté droit.
fn main() { let mut greeting = String::from("Bonjour, "); // possédée, donc elle peut grandir let name = String::from("Ferris");
greeting += &name; // on emprunte le côté droit greeting.push_str(" !"); // la même chose, sous forme de méthode
let mut tag = "crabe".to_string(); // &str à gauche ? on en fait une String tag += " n°1";
println!("{greeting}"); println!("{tag}"); println!("{name} est toujours utilisable");}Sortie :
Bonjour, Ferris !crabe n°1Ferris est toujours utilisableComme le côté droit est seulement emprunté, name vous appartient toujours après coup. += et push_str font exactement la même chose : prenez celui qui se lit le mieux. Voir Les chaînes de caractères.
Solution 4 : ajouter une contrainte T: AddAssign dans le code générique
Section intitulée « Solution 4 : ajouter une contrainte T: AddAssign dans le code générique »Choisissez cette solution quand l’erreur dit cannot be applied to type `T`. Dans une fonction générique, T pourrait être n’importe quel type, même un type sans +=. L’aide de rustc dit exactement quoi ajouter : consider further restricting type parameter `T` with trait `AddAssign` (pensez à restreindre davantage le paramètre de type T avec le trait 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]));}Sortie :
60.75Désormais, total marche pour tout type qui peut être copié, qui a une valeur de départ et qui prend en charge += : les entiers, les flottants, et votre propre Money dès qu’il implémente ces traits. Pour de simples nombres, items.iter().sum() fait le même travail en une ligne. Voir Les génériques.
Où vous le verrez souvent
Section intitulée « Où vous le verrez souvent »Compter avec une HashMap. let count = counts.entry(word).or_insert(0); suivi de count += 1; donne cannot be applied to type `&mut i32`. count est une référence, pas le nombre. L’aide de rustc : « += can be used on i32 if you dereference the left-hand side » (+= est utilisable sur i32 si vous déréférencez le côté gauche). Correction : *count += 1;. Voir Les HashMap.
Multiplier une struct par un nombre. p *= 2 demande MulAssign<{integer}>, parce que le côté droit est un nombre, pas un autre Point. Correction : implémentez le trait pour ce type de droite :
impl std::ops::MulAssign<i32> for Point { fn mul_assign(&mut self, factor: i32) { self.x *= factor; self.y *= factor; }}Des totaux cumulés dans une boucle. total += item.price où price est votre propre type Money. La solution 1 règle le problème. Si vous préférez écrire prices.iter().copied().sum(), il vous faut alors le trait Sum, et sans lui vous obtenez E0277 (« a value of type Money cannot be made by summing an iterator »).
Erreurs liées
Section intitulée « Erreurs liées »- E0369 : binary operation cannot be applied : le même problème pour
+,==,<et les autres opérateurs sans affectation. - E0308 : mismatched types : ce que vous obtenez pour
s += tavec deuxString(empruntez :s += &t). - E0277 : trait bound not satisfied :
+=existe pour le type de gauche, mais pas avec ce type de droite, commecannot add-assign `{float}` to `{integer}`pourtotal += 2.5quandtotalest un entier. - E0384 : cannot assign twice :
x += 1sur une variable déclarée sansmut. L’opérateur va bien, c’est la variable qui n’est pas mutable. - Toutes les erreurs du compilateur : la liste complète des pages d’erreurs.
Petit quiz
Section intitulée « Petit quiz »Petit quiz
1. Point implémente Add, mais p += q donne quand même E0368. Pourquoi ?
2. count vient de map.entry(word).or_insert(0). Quelle ligne compile ?
3. s et t sont toutes les deux des String. Comment ajouter t à la fin de s ?
Score : 0 / 3
Pour aller plus loin
Section intitulée « Pour aller plus loin »- E0368 — Rust error codes index (en anglais) : l’explication officielle de cette erreur.
std::ops::AddAssign— documentation de la bibliothèque standard (en anglais) : le trait derrière+=, avec un exemple ;SubAssign,MulAssignet les autres sont listés dansstd::ops.- Advanced Traits — The Rust Book (en anglais) : la surcharge d’opérateurs avec
std::opset les paramètres de type par défaut commeRhs. - Storing UTF-8 Encoded Text with Strings — The Rust Book (en anglais) : pourquoi les chaînes grandissent avec
push_stret pourquoi+prend un&strà droite. - Operator Overloading — Rust by Example (en anglais) : un court exemple exécutable qui implémente des traits d’opérateurs pour vos propres types.