Salta ai contenuti

Errore Rust E0369: binary operation cannot be applied to type (operatore mancante)

E0369 significa che hai usato un operatore come ==, < o + su un tipo che non sa ancora come gestirlo. In Rust gli operatori non sono incorporati nelle tue struct e nei tuoi enum. Ognuno arriva da un trait, e il tuo tipo lo ottiene solo quando derivi o implementi quel trait. La soluzione di solito è una sola riga #[derive(...)], e il compilatore ti dice perfino quale. Per gli altri errori di questa serie, vedi Errori del compilatore Rust.

error[E0369]: binary operation `==` cannot be applied to type `Point`
--> src/main.rs:9:10
|
9 | if a == b { // ❌ Point doesn't know how to compare itself
| - ^^ - 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 {
|

Il compilatore dice: «l’operazione binaria == non si può applicare al tipo Point». Questo è il programma che lo produce:

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 doesn't know how to compare itself
println!("Same point!");
}
}

La prima riga cambia a seconda dell’operatore, ma l’errore è lo stesso:

  • binary operation `<` cannot be applied to type `Version`: manca PartialOrd (vale anche per >, <=, >=).
  • cannot add `Point` to `Point`: manca Add. Vedrai anche «cannot subtract», «cannot multiply» e così via.
  • cannot add `&str` to `&str`: hai provato a unire due slice di stringa con +.

Le etichette sotto l’operatore mostrano il tipo da ciascun lato, qui Point e Point. Poi la note: indica la tua struct e dice che deve implementare PartialEq. In altre parole: «mi hai chiesto di confrontare due Point, ma nessuno mi ha detto cosa significa uguale per un Point».

Pensa a un arbitro a cui vengono dati due oggetti con la domanda «qual è il più grande?». Per i numeri la risposta è ovvia. Per due zaini, all’arbitro serve prima una regola: per peso? per volume? Rust è quell’arbitro scrupoloso.

Ogni operatore è una chiamata di metodo travestita. a == b chiama PartialEq::eq, a < b chiama PartialOrd::partial_cmp, e a + b chiama Add::add. I numeri, String e molti tipi standard implementano già questi trait. La tua struct nuova parte senza nessuno di loro.

Rust non tira a indovinare, perché a volte sbaglierebbe. Due User dovrebbero essere uguali quando coincidono gli ID, o solo quando coincidono tutti i campi? Point + Point dovrebbe sommare le coordinate, o sommare dei punti non ha nemmeno senso? Decidi tu, derivando il trait (il comportamento predefinito, campo per campo) o scrivendo il tuo impl. I trait sono spiegati nella pagina Trait comuni.

Scegli questa per == e != quando «uguale» significa «tutti i campi sono uguali». Nella maggior parte dei casi è la soluzione giusta, ed è proprio quella che suggerisce rustc. Se aggiungi anche Debug, puoi stampare la struct:

#[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!("Stesso punto!");
}
println!("{:?} != {:?}: {}", a, c, a != c);
}

Output:

Stesso punto!
Point { x: 1, y: 2 } != Point { x: 5, y: 0 }: true
▶ Provalo tu

Anche ogni campo deve essere confrontabile. Se al tipo di un campo manca PartialEq, derivalo anche su quel tipo. Gli enum funzionano allo stesso modo: #[derive(PartialEq)] su enum Status fa compilare status == Status::Banned.

Scegli questa quando il tuo tipo ha un ordine naturale. PartialOrd richiede PartialEq, quindi rustc suggerisce #[derive(PartialEq, PartialOrd)]. La versione derivata confronta i campi dall’alto verso il basso: prima major, e solo se sono uguali, minor. Quindi l’ordine dei campi nella struct conta:

#[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!("Aggiorna da {:?}", installed);
}
let next = Version { major: 2, minor: 0 };
println!("2.0 è più recente di 1.7: {}", next > required);
}

Output:

Aggiorna da Version { major: 1, minor: 4 }
2.0 è più recente di 1.7: true
▶ Provalo tu

Se vuoi fare .sort() su un Vec del tuo tipo, ti serviranno anche Eq e Ord. Vedi Ordinamento.

Scegli questa quando sommare due valori del tuo tipo ha un significato chiaro, come sommare coordinate o importi. Per gli operatori matematici non esiste un derive, quindi scrivi un breve impl. Output è il tipo che restituisce il +:

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 });
}

Output:

Point { x: 4, y: 6 }
true
▶ Provalo tu

Sub, Mul, Div e Neg funzionano allo stesso modo, tutti in std::ops. Derivare Copy qui significa che start e step restano usabili dopo il +. Trovi di più in Trait comuni e Trait avanzati.

Scegli questa quando l’errore dice cannot add `&str` to `&str`. Il + sulle stringhe vuole una String posseduta a sinistra e un &str a destra. L’help di rustc lo dice a modo suo: «create an owned String from a string reference» (crea una String posseduta da un riferimento a stringa), quindi first.to_owned() + second. Con due String, prendi in prestito quella a destra: s1 + &s2. E format! funziona sempre e non sposta niente:

fn main() {
let first = String::from("Ciao, ");
let second = String::from("Ferris");
let shout = format!("{first}{second}!"); // prende in prestito entrambe
let joined = first + &second; // first viene spostata in joined
let short = "Ehi, ".to_owned() + "granchio"; // &str a sinistra: prima rendila posseduta
println!("{shout}");
println!("{joined}");
println!("{short}");
println!("{second} è ancora qui");
}

Output:

Ciao, Ferris!
Ciao, Ferris
Ehi, granchio
Ferris è ancora qui
▶ Provalo tu

+ prende l’ownership della String di sinistra e ci aggiunge il testo, per questo first non si può più usare dopo quella riga. Se ti confonde, usa semplicemente format!. Vedi Stringhe.

Confronti nel codice generico. La classica funzione largest fallisce con binary operation `>` cannot be applied to type `&T`, perché T potrebbe essere un tipo qualsiasi, anche uno senza un ordine:

fn largest<T>(list: &[T]) -> &T {
let mut largest = &list[0];
for item in list {
if item > largest { // ❌ T might not be comparable
largest = item;
}
}
largest
}

Soluzione: aggiungi un vincolo di trait (trait bound), proprio come suggerisce rustc: fn largest<T: PartialOrd>(list: &[T]) -> &T. Vedi Generici.

Controllare il valore di un enum. if status == Status::Banned fallisce finché l’enum non ha #[derive(PartialEq)]. Se non puoi cambiare l’enum (arriva da un altro crate), usa matches!(status, Status::Banned) oppure un match.

Unire due String. first + second con due String non è E0369: è E0308 («expected &str, found String») con help: consider borrowing here, first + &second. Stessa famiglia, stessa soluzione della Soluzione 4.

Confrontare due tipi diversi. count == limit con un i32 e un f64 dà E0308, e name == 'F' con una String e un char dà E0277 («can’t compare String with char»). Una regola pratica: ottieni E0369 quando il tipo a sinistra non ha nessuna versione dell’operatore, ed E0308 o E0277 quando ne ha una, ma non per quel tipo a destra. Soluzione: converti uno dei due lati (count as f64 == limit, name == "F").

Verifica veloce

1. Scrivi if order_a == order_b per una struct Order e ottieni E0369. Di solito qual è la soluzione?

2. Con #[derive(PartialOrd)] su struct Version { major: u32, minor: u32 }, come vengono confrontate due versioni?

3. first e second sono entrambi &str. Quale espressione li unisce senza errori?

Punteggio: 0 / 3