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.)
Il messaggio di errore
Sezione intitolata “Il messaggio di errore”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) insteadIl 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);}Cosa significa
Sezione intitolata “Cosa significa”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.
Perché Rust si comporta così
Sezione intitolata “Perché Rust si comporta così”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.
Come risolverlo
Sezione intitolata “Come risolverlo”E0277 ha tante facce. Trova qui sotto il messaggio che corrisponde al tuo.
Soluzione 1: stampare una struct con {} o {:?}
Sezione intitolata “Soluzione 1: stampare una struct con {} o {:?}”{:?} 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à {:?} gratisstruct 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)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: trueAttività Low: 1Un 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.5Funziona 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.
Soluzione 4: ? non riesce a convertire l’errore
Sezione intitolata “Soluzione 4: ? non riesce a convertire l’errore”error[E0277]: `?` couldn't convert the error to `ConfigError` |8 | fn parse_port(text: &str) -> Result<u16, ConfigError> { | ------------------------ expected `ConfigError` because of this9 | 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` traitIl 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 }))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: debugmain ha ancora: debugVale lo stesso per RefCell: tra thread, usa Arc<Mutex<T>> invece di Rc<RefCell<T>>. Vedi Stato condiviso e Concorrenza.
Dove lo incontri di solito
Sezione intitolata “Dove lo incontri di solito”- 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 inannounce» (richiesto da un vincolo inannounce). Soluzione: implementa il trait richiesto dal vincolo. - Test.
assert_eq!(a, b)richiede siaPartialEqsiaDebug(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 typeVec<i32>cannot be built from an iterator over elements of type&String» (unVec<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.
Errori correlati
Sezione intitolata “Errori correlati”- E0599: no method found: chiamare
.insert()su unaHashMapla cui chiave non èHasha volte compare come E0599 («trait bounds were not satisfied») invece che come E0277. Stessa soluzione: deriva i trait. - E0308: mismatched types: hai dato un tipo dove ne era atteso un altro, come un
i32dove va unf64. - E0382: borrow of moved value: compare spesso subito dopo che hai aggiunto
CloneoCopypensando ai trait. E0369: binary operation == cannot be applied: la versione dell’operatore==delPartialEqmancante. Stessa soluzione.- Tutti gli errori del compilatore spiegati
Verifica veloce
Sezione intitolata “Verifica veloce”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
Per approfondire
Sezione intitolata “Per approfondire”- E0277 — Rust error codes index (in inglese): la spiegazione ufficiale di questo errore.
- Traits: Defining Shared Behavior — The Rust Book (in inglese): che cosa sono i trait e come funzionano i vincoli di trait.
- Derivable Traits — The Rust Book, Appendix C (in inglese): che cosa ti danno
Debug,PartialEq,Eq,Hash,PartialOrdeOrdquando li derivi. - Recoverable Errors with Result — The Rust Book (in inglese): come funziona
?, compresa la sua conversione conFrom. Send— libreria standard di Rust (in inglese): quali tipi possono spostarsi tra thread, e perchéRcnon può.