Salta ai contenuti

Errore Rust E0277: the trait bound is not satisfied (trait non implementato)

L’errore E0277 significa: hai usato un tipo in un punto che richiede una capacità che quel tipo non ha. Stamparlo, confrontarlo, sommarlo, convertirlo, mandarlo a un altro thread: ognuna di queste cose richiede un trait, e al tuo tipo manca quel trait. È uno degli errori di Rust più comuni, il messaggio di solito ti dice la soluzione esatta, e nella maggior parte dei casi la soluzione è una riga sola. (Questa pagina fa parte di Errori del compilatore Rust spiegati.)

La versione che quasi tutti incontrano per prima è stampare una propria struct con {}:

error[E0277]: `Player` doesn't implement `std::fmt::Display`
--> src/main.rs:8:20
|
8 | println!("{}", p);
| -- ^ `Player` cannot be formatted with the default formatter
| |
| required by this formatting parameter
|
help: the trait `std::fmt::Display` is not implemented for `Player`
= note: in format strings you may be able to use `{:?}` (or {:#?} for pretty-print) instead

Il compilatore ti dice che Player non implementa std::fmt::Display, quindi non si può formattare con il formattatore predefinito, e ti suggerisce di provare {:?}.

Il programma che lo provoca:

struct Player {
name: String,
score: u32,
}
fn main() {
let p = Player { name: String::from("Ana"), score: 42 };
println!("{}", p);
}

Un trait è una promessa che un tipo sa fare qualcosa. Display è la promessa «so stamparmi in modo leggibile». PartialEq è «posso essere confrontato con ==». Send è «posso essere spostato in sicurezza in un altro thread».

Codice come println!, .sort(), + e thread::spawn chiede una di queste promesse. Il compilatore indica il valore che hai passato (p alla riga 8) e dice: questo tipo non ha mai fatto quella promessa, quindi qui non posso usarlo.

Pensa a una presa elettrica. La presa accetta solo spine di una certa forma. Il tuo apparecchio può funzionare benissimo, ma se la spina non è quella giusta, non entra. E0277 è il compilatore che ti dice «spina sbagliata». Lo risolvi dando al tipo la spina giusta: di solito con #[derive(...)], a volte con un breve blocco impl.

Rust non tira mai a indovinare. Per il tuo tipo Player non esiste un unico modo «giusto» per stamparlo, confrontarlo o ordinarlo. Due giocatori sono uguali quando hanno lo stesso nome, o solo quando coincidono tutti i campi? Rust ti obbliga a dirlo, invece di fare in silenzio qualcosa che non intendevi.

Il controllo avviene durante la compilazione. In molti linguaggi, stampare un oggetto a cui non hai mai insegnato a stamparsi ti dà qualcosa di inutile come <Player object at 0x7f...>, e mandare a un thread un valore non sicuro tra thread ti regala un crash raro in produzione. In Rust, entrambi sono errori di compilazione che correggi una volta per tutte.

Puoi leggere di più su come funzionano i trait nella pagina Trait, e su quelli di tutti i giorni (Debug, Display, PartialEq, Ord, Hash) in Trait comuni.

E0277 ha tante facce. Trova qui sotto il messaggio che corrisponde al tuo.

{:?} richiede Debug. Non lo scrivi quasi mai a mano: aggiungi #[derive(Debug)]. Usalo per il debug e i log. La versione dell’errore per {:?} è questa:

error[E0277]: `Player` doesn't implement `Debug`
= help: the trait `Debug` is not implemented for `Player`
= note: add `#[derive(Debug)]` to `Player` or manually `impl Debug for Player`

Qui il compilatore ti suggerisce proprio di aggiungere #[derive(Debug)] a Player, oppure di scrivere a mano impl Debug for Player.

{} richiede Display. Rust non può derivarlo, perché solo tu sai come deve essere la versione «leggibile». Scrivi un piccolo impl con write!. Usalo per il testo che vedranno i tuoi utenti.

use std::fmt;
#[derive(Debug)] // ti dà {:?} gratis
struct Player {
name: String,
score: u32,
}
// Display lo scrivi tu: decide che cosa stampa {}.
impl fmt::Display for Player {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "{} ({} punti)", self.name, self.score)
}
}
fn main() {
let p = Player { name: String::from("Ana"), score: 42 };
println!("{:?}", p);
println!("{}", p);
}

Output:

Player { name: "Ana", score: 42 }
Ana (42 punti)
▶ Provalo tu

Soluzione 2: confrontare, ordinare o usare come chiave di una HashMap

Sezione intitolata “Soluzione 2: confrontare, ordinare o usare come chiave di una HashMap”

.contains() e == richiedono PartialEq. .sort() richiede Ord. Le chiavi di HashSet e HashMap richiedono Eq e Hash. I messaggi sono questi:

error[E0277]: can't compare `Point` with `Point`
= help: the trait `PartialEq` is not implemented for `Point`
error[E0277]: the trait bound `Priority: Ord` is not satisfied
= help: the trait `Ord` is not implemented for `Priority`
error[E0277]: the trait bound `Point: Hash` is not satisfied
= help: the trait `Hash` is not implemented for `Point`

In ordine: «non posso confrontare Point con Point», «il vincolo Priority: Ord non è soddisfatto», «il vincolo Point: Hash non è soddisfatto». La soluzione è derivarli. Si appoggiano l’uno all’altro, quindi il set completo di solito è PartialEq, Eq, Hash, PartialOrd, Ord:

use std::collections::HashMap;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)]
enum Priority {
Low,
Medium,
High,
}
fn main() {
let mut tasks = vec![Priority::High, Priority::Low, Priority::Medium];
tasks.sort(); // richiede Ord
println!("{:?}", tasks);
println!("Contiene High: {}", tasks.contains(&Priority::High)); // richiede PartialEq
let mut counts: HashMap<Priority, u32> = HashMap::new(); // la chiave richiede Eq + Hash
for task in &tasks {
*counts.entry(*task).or_insert(0) += 1;
}
println!("Attività Low: {}", counts[&Priority::Low]);
}

Output:

[Low, Medium, High]
Contiene High: true
Attività Low: 1
▶ Provalo tu

Un Ord derivato ordina le varianti di un enum nell’ordine in cui le hai scritte, e le struct campo per campo dall’alto in basso. Un limite, detto sinceramente: un tipo con un campo f64 non può derivare Eq, Hash o Ord, perché i numeri in virgola mobile hanno NaN, che non è uguale a se stesso. Per ordinare i numeri decimali, vedi Ordinamento e ricerca.

Soluzione 3: mescolare tipi numerici (cannot add f64 to i32)

Sezione intitolata “Soluzione 3: mescolare tipi numerici (cannot add f64 to i32)”
error[E0277]: cannot add `f64` to `i32`
--> src/main.rs:4:23
|
4 | let total = items + price;
| ^ no implementation for `i32 + f64`
|
= help: the trait `Add<f64>` is not implemented for `i32`

Il compilatore dice che non può sommare un f64 a un i32: non esiste un’implementazione di i32 + f64. Rust non converte mai i numeri al posto tuo, nemmeno da i32 a f64. I due lati di +, -, * e / devono avere lo stesso tipo. Converti tu uno dei due lati con as:

fn main() {
let items: i32 = 3;
let price: f64 = 2.5;
let total = items as f64 * price; // prima converti, poi moltiplica
println!("Totale: {total}");
}

Output:

Totale: 7.5
▶ Provalo tu

Funziona anche f64::from(items), che non può mai perdere dati. Nella direzione opposta (da f64 a i32) i decimali vengono tagliati, quindi pensa prima all’arrotondamento. Le pagine Conversione di tipo (cast) e Conversioni spiegano i dettagli.

error[E0277]: `?` couldn't convert the error to `ConfigError`
|
8 | fn parse_port(text: &str) -> Result<u16, ConfigError> {
| ------------------------ expected `ConfigError` because of this
9 | let port = text.parse::<u16>()?;
| --------------^ the trait `From<ParseIntError>` is not implemented for `ConfigError`
|
= note: the question mark operation (`?`) implicitly performs a conversion on the error value using the `From` trait

Il compilatore dice che ? non è riuscito a convertire l’errore in ConfigError, perché manca From<ParseIntError>, e ricorda che ? converte implicitamente l’errore usando il trait From. Quando ? incontra un errore, lo restituisce dalla tua funzione. Se il tipo dell’errore è diverso da quello che la funzione promette, ? prova a convertirlo con From. Il tuo compito è scrivere quel From:

use std::num::ParseIntError;
#[derive(Debug)]
enum ConfigError {
BadPort(ParseIntError),
}
// Insegna a `?` come trasformare un ParseIntError in un ConfigError.
impl From<ParseIntError> for ConfigError {
fn from(err: ParseIntError) -> Self {
ConfigError::BadPort(err)
}
}
fn parse_port(text: &str) -> Result<u16, ConfigError> {
let port = text.parse::<u16>()?;
Ok(port)
}
fn main() {
println!("{:?}", parse_port("8080"));
println!("{:?}", parse_port("ottanta"));
}

Output:

Ok(8080)
Err(BadPort(ParseIntError { kind: InvalidDigit }))
▶ Provalo tu

Altre buone strade: convertire una sola chiamata con .map_err(ConfigError::BadPort)?, usare #[from] del crate thiserror, oppure restituire Result<T, Box<dyn std::error::Error>> nei programmi piccoli. Vedi Errori personalizzati e anyhow e thiserror.

Soluzione 5: Rc non si può inviare tra thread in sicurezza

Sezione intitolata “Soluzione 5: Rc non si può inviare tra thread in sicurezza”
error[E0277]: `Rc<String>` cannot be sent between threads safely
--> src/main.rs:6:32
|
6 | let handle = thread::spawn(move || {
| ------------- required by a bound introduced by this call
...
= help: within `{closure@src/main.rs:6:32: 6:39}`, the trait `Send` is not implemented for `Rc<String>`

Il compilatore dice che Rc<String> non si può inviare tra thread in sicurezza, perché non implementa Send. Rc conta i suoi proprietari con un semplice numero, che due thread potrebbero modificare nello stesso momento, rovinandolo. Per questo Rc non è Send. Usa invece Arc (la versione «atomica»). Funziona allo stesso modo ed è sicuro tra thread:

use std::sync::Arc;
use std::thread;
fn main() {
let config = Arc::new(String::from("debug"));
let for_thread = Arc::clone(&config); // un secondo proprietario per il thread
let handle = thread::spawn(move || {
println!("Il thread vede: {for_thread}");
});
handle.join().unwrap();
println!("main ha ancora: {config}");
}

Output:

Il thread vede: debug
main ha ancora: debug
▶ Provalo tu

Vale lo stesso per RefCell: tra thread, usa Arc<Mutex<T>> invece di Rc<RefCell<T>>. Vedi Stato condiviso e Concorrenza.

  • Funzioni generiche. fn announce<T: Display>(item: T) chiamata con la tua struct dà `Song` doesn't implement `std::fmt::Display` con «required by a bound in announce» (richiesto da un vincolo in announce). Soluzione: implementa il trait richiesto dal vincolo.
  • Test. assert_eq!(a, b) richiede sia PartialEq sia Debug (per stampare i valori quando il test fallisce). Soluzione: #[derive(Debug, PartialEq)].
  • .collect() nel tipo sbagliato. let nums: Vec<i32> = words.iter().collect(); dà «a value of type Vec<i32> cannot be built from an iterator over elements of type &String» (un Vec<i32> non si può costruire da un iteratore di &String). Soluzione: prima converti ogni elemento, per esempio con .map(|w| w.parse().unwrap()). Vedi Iteratori.

Verifica veloce

1. Ottieni "Order doesn't implement Debug" usando {:?}. Qual è la soluzione più veloce?

2. Perché let total = 3_i32 + 2.5_f64; non funziona?

3. Il tuo codice con i thread fallisce con "Rc<String> cannot be sent between threads safely". Che cosa dovresti usare?

Punteggio: 0 / 3