Salta ai contenuti

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.

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».

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.

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.

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 }
▶ Provalo tu

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 }
▶ Provalo tu

*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.

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 una String.
  • s += t, dove anche t è una String, non è E0368. È E0308, «expected &str, found String», con l’help s += &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 #1
Ferris è ancora utilizzabile
▶ Provalo tu

Visto 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]) -> T
where
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:

6
0.75
▶ Provalo tu

Ora 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.

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»).

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