Errore Rust E0368: binary assignment operation += cannot be applied to type
E0368 significa che hai usato un operatore composto come +=, -= o *= su un tipo che non lo supporta ancora. È il cugino con += di E0369. Le tue struct non ricevono += in automatico, nemmeno quando supportano già +. La soluzione di solito è un breve blocco impl AddAssign, e sui numeri dietro un riferimento basta un *. Per gli altri errori di questa serie, vedi Errori del compilatore Rust.
Il messaggio di errore
Sezione intitolata “Il messaggio di errore”error[E0368]: binary assignment operation `+=` cannot be applied to type `Money` --> src/main.rs:8:5 |8 | wallet += tip; // ❌ Money doesn't know how to do += | ------^^^^^^^ | | | 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`Il compilatore dice: «l’operazione di assegnazione += non si può applicare al tipo Money». Questo è il programma che lo produce:
struct Money { cents: i64,}
fn main() { let mut wallet = Money { cents: 500 }; let tip = Money { cents: 150 }; wallet += tip; // ❌ Money doesn't know how to do += println!("{}", wallet.cents);}L’operatore nella prima riga cambia, ma l’errore è lo stesso. p -= step chiede SubAssign, e p *= 2 chiede MulAssign<{integer}>, cioè «moltiplica-e-assegna per un numero intero».
Cosa significa
Sezione intitolata “Cosa significa”L’etichetta sotto il lato sinistro dice cosa è andato storto: «cannot use += on type Money» (non puoi usare += sul tipo Money). Poi la note: indica la tua struct e dice che deve implementare AddAssign. È il trait che sta dietro a +=.
Pensa a un salvadanaio. Infilarci una moneta è un’azione chiara. Ma se qualcuno ti dà una scatola con scritto «Money» e ti dice «aggiungici quest’altra scatola», devi sapere come: sommare i centesimi? impilare le scatole? Rust ha bisogno che tu scriva quella regola una volta.
Perché Rust si comporta così
Sezione intitolata “Perché Rust si comporta così”Ogni operatore composto è una chiamata di metodo travestita. a += b chiama AddAssign::add_assign(&mut a, b), a -= b chiama SubAssign::sub_assign, e così via. Tutti questi trait stanno in std::ops. I numeri li implementano già. La tua struct nuova parte senza nessuno di loro, e per gli operatori matematici non esiste un #[derive].
Ecco la parte che sorprende: Add e AddAssign sono trait separati. Anche se Point implementa già Add, p += q dà comunque E0368. Il motivo è che fanno lavori diversi. a + b prende due valori e restituisce un valore nuovo. a += b cambia a sul posto tramite &mut self e non restituisce niente. Lavorare sul posto può costare molto meno: il += di String aggiunge al buffer esistente invece di costruire una stringa nuova. Rust lascia scegliere a ogni tipo, quindi non trasforma mai in silenzio += in + al posto tuo.
I trait degli operatori sono spiegati in Trait comuni e Trait avanzati.
Come risolverlo
Sezione intitolata “Come risolverlo”Soluzione 1: Implementa AddAssign (e i suoi fratelli)
Sezione intitolata “Soluzione 1: Implementa AddAssign (e i suoi fratelli)”Scegli questa quando += sul tuo tipo ha un significato chiaro, come aggiungere denaro o spostare una posizione. Scrivi un solo metodo che cambia self. Aggiungi allo stesso modo SubAssign, MulAssign e gli altri quando ti servono:
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 }; // ricevuta una mancia wallet -= Money { cents: 200 }; // pagato il pranzo println!("{:?}", wallet);}Output:
Money { cents: 450 }Nota &mut self e l’assenza di un tipo di ritorno. È tutta qui la differenza rispetto ad Add. All’interno, self.cents += other.cents funziona perché i64 ha già AddAssign.
Soluzione 2: Hai già Add? Scrivi a = a + b, oppure riusalo
Sezione intitolata “Soluzione 2: Hai già Add? Scrivi a = a + b, oppure riusalo”Scegli questa quando il tuo tipo implementa già Add (magari dopo aver risolto E0369). Se il tipo è Copy, puoi semplicemente scrivere pos = pos + step, che richiede solo Add. Oppure implementa AddAssign in una riga chiamando il tuo Add, così funziona anche +=:
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; // riusa Add }}
fn main() { let mut pos = Point { x: 0, y: 0 }; let step = Point { x: 1, y: 2 }; pos = pos + step; // richiede solo Add pos += step; // richiede AddAssign println!("{:?}", pos);}Output:
Point { x: 2, y: 4 }*self = *self + other copia fuori il valore attuale, fa la somma e riscrive il risultato. Funziona solo perché Point è Copy. Per un tipo che possiede dati sull’heap (come un campo Vec), scrivi invece la versione sul posto della Soluzione 1.
Soluzione 3: Stringhe: s += &t o push_str
Sezione intitolata “Soluzione 3: Stringhe: s += &t o push_str”Scegli questa quando a sinistra c’è una stringa. += funziona su una String posseduta, e a destra deve esserci un &str. Due casi mettono spesso in difficoltà:
cannot be applied to type `&str`: a sinistra c’è una slice di stringa come"Ciao, ". Un&strè una vista di sola lettura, quindi non può crescere. Prima trasformalo in unaString.s += t, dove anchetè unaString, non è E0368. È E0308, «expected&str, foundString», con l’helps += &t. Basta prendere in prestito il lato destro.
fn main() { let mut greeting = String::from("Ciao, "); // posseduta, quindi può crescere let name = String::from("Ferris");
greeting += &name; // prendi in prestito il lato destro greeting.push_str("!"); // la stessa cosa, come metodo
let mut tag = "granchio".to_string(); // &str a sinistra? trasformalo in String tag += " #1";
println!("{greeting}"); println!("{tag}"); println!("{name} è ancora utilizzabile");}Output:
Ciao, Ferris!granchio #1Ferris è ancora utilizzabileVisto che il lato destro è solo preso in prestito, dopo name è ancora tuo. += e push_str fanno esattamente la stessa cosa; usa quello che si legge meglio. Vedi Stringhe.
Soluzione 4: Aggiungi un vincolo T: AddAssign nel codice generico
Sezione intitolata “Soluzione 4: Aggiungi un vincolo T: AddAssign nel codice generico”Scegli questa quando l’errore dice cannot be applied to type `T`. Dentro una funzione generica, T potrebbe essere qualsiasi tipo, anche uno senza +=. L’help di rustc dice esattamente cosa aggiungere: consider further restricting type parameter `T` with trait `AddAssign` (valuta di restringere ulteriormente T con il 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]));}Output:
60.75Ora total funziona con qualsiasi tipo che si può copiare, ha un valore iniziale e supporta +=: interi, numeri in virgola mobile, e anche il tuo Money una volta che implementa quei trait. Per i numeri semplici, items.iter().sum() fa lo stesso lavoro in una riga. Vedi Generici.
Dove lo incontri di solito
Sezione intitolata “Dove lo incontri di solito”Contare con una HashMap. let count = counts.entry(word).or_insert(0); e poi count += 1; dà cannot be applied to type `&mut i32`. count è un riferimento, non il numero. L’help di rustc: «+= can be used on i32 if you dereference the left-hand side» (+= si può usare su i32 se dereferenzi il lato sinistro). Soluzione: *count += 1;. Vedi HashMap.
Scalare una struct per un numero. p *= 2 richiede MulAssign<{integer}>, perché a destra c’è un numero, non un altro Point. Soluzione: implementa il trait per quel tipo a destra:
impl std::ops::MulAssign<i32> for Point { fn mul_assign(&mut self, factor: i32) { self.x *= factor; self.y *= factor; }}Totali progressivi in un ciclo. total += item.price, dove price è il tuo tipo Money. La Soluzione 1 lo fa funzionare. Se preferisci scrivere prices.iter().copied().sum(), ti serve invece il trait Sum, e senza ottieni E0277 («a value of type Money cannot be made by summing an iterator»).
Errori correlati
Sezione intitolata “Errori correlati”- E0369: binary operation cannot be applied: lo stesso problema per
+,==,<e gli altri operatori che non assegnano. - E0308: mismatched types: quello che ottieni per
s += tcon dueString(prendila in prestito:s += &t). - E0277: trait bound not satisfied:
+=esiste per il tipo a sinistra, ma non con questo tipo a destra, comecannot add-assign `{float}` to `{integer}`pertotal += 2.5quandototalè un intero. - E0384: cannot assign twice:
x += 1su una variabile dichiarata senzamut. L’operatore va bene, è la variabile a non essere mutabile. - Tutti gli errori del compilatore: l’elenco completo delle pagine sugli errori.
Verifica veloce
Sezione intitolata “Verifica veloce”Verifica veloce
1. Point implementa Add, ma p += q dà comunque E0368. Perché?
2. count arriva da map.entry(word).or_insert(0). Quale riga compila?
3. s e t sono entrambe String. Come aggiungi t in fondo a s?
Punteggio: 0 / 3
Per approfondire
Sezione intitolata “Per approfondire”- E0368 — Rust error codes index (in inglese): la spiegazione ufficiale di questo errore.
std::ops::AddAssign— standard library docs (in inglese): il trait dietro a+=, con un esempio;SubAssign,MulAssigne gli altri sono elencati instd::ops(in inglese).- Advanced Traits — The Rust Book (in inglese): l’overloading degli operatori con
std::opse i parametri di tipo predefiniti comeRhs. - Storing UTF-8 Encoded Text with Strings — The Rust Book (in inglese): perché le stringhe crescono con
push_stre perché+vuole un&stra destra. - Operator Overloading — Rust by Example (in inglese): un breve esempio eseguibile di come implementare i trait degli operatori per i tuoi tipi.