Erreur Rust E0277 : the trait bound is not satisfied (trait manquant)
L’erreur E0277 veut dire : vous avez utilisé un type à un endroit qui demande une capacité que ce type n’a pas. L’afficher, le comparer, l’additionner, le convertir, l’envoyer à un autre thread : chacune de ces actions demande un trait (une capacité qu’un type déclare avoir), et il manque ce trait à votre type. C’est l’une des erreurs Rust les plus courantes, le message indique en général la correction exacte, et la plupart du temps elle tient en une ligne. (Cette page fait partie de la série Les erreurs du compilateur Rust expliquées.)
Le message d’erreur
Section intitulée « Le message d’erreur »La version que la plupart des gens rencontrent en premier, c’est l’affichage de leur propre struct avec {} :
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) insteadLe compilateur dit que Player n’implémente pas Display et ne peut donc pas être affiché avec le format par défaut. Il suggère d’essayer {:?} à la place.
Le programme qui la provoque :
struct Player { name: String, score: u32,}
fn main() { let p = Player { name: String::from("Ana"), score: 42 }; println!("{}", p);}Ce que ça veut dire
Section intitulée « Ce que ça veut dire »Un trait est une promesse : le type sait faire quelque chose. Display, c’est la promesse « je sais m’afficher joliment ». PartialEq, c’est « on peut me comparer avec == ». Send, c’est « je peux être envoyé sans danger à un autre thread ».
Du code comme println!, .sort(), + ou thread::spawn réclame l’une de ces promesses. Le compilateur montre la valeur que vous avez passée (p à la ligne 8) et dit : ce type n’a jamais fait cette promesse, donc je ne peux pas l’utiliser ici.
Pensez à une prise électrique. La prise n’accepte qu’une seule forme de fiche. Votre appareil peut très bien fonctionner, mais si sa fiche n’a pas la bonne forme, elle ne rentre pas. E0277, c’est le compilateur qui vous dit « mauvaise fiche ». Pour corriger, vous donnez au type la bonne fiche : le plus souvent avec #[derive(...)], parfois avec un petit bloc impl.
Pourquoi Rust fait ça
Section intitulée « Pourquoi Rust fait ça »Rust ne devine jamais. Pour votre propre type Player, il n’existe pas une seule « bonne » façon de l’afficher, de le comparer ou de le trier. Deux joueurs sont-ils égaux quand leurs noms sont identiques, ou seulement quand tous les champs le sont ? Rust vous demande de le dire, plutôt que de faire en douce quelque chose que vous ne vouliez pas.
La vérification a lieu à la compilation. Dans beaucoup de langages, afficher un objet à qui on n’a jamais appris à s’afficher donne un résultat inutile comme <Player object at 0x7f...>, et envoyer à un thread une valeur qui n’est pas thread-safe provoque un plantage rare, en production. En Rust, ce sont deux erreurs de compilation que vous corrigez une fois pour toutes.
Vous en saurez plus sur le fonctionnement des traits dans Les traits, et sur ceux du quotidien (Debug, Display, PartialEq, Ord, Hash) dans Les traits courants.
Comment corriger
Section intitulée « Comment corriger »E0277 a plusieurs visages. Cherchez ci-dessous le message qui ressemble au vôtre.
Solution 1 : afficher une struct avec {} ou {:?}
Section intitulée « Solution 1 : afficher une struct avec {} ou {:?} »{:?} demande Debug. Vous ne l’écrivez presque jamais à la main : ajoutez #[derive(Debug)]. Servez-vous-en pour le débogage et les logs. La version {:?} de l’erreur ressemble à ceci :
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`La note propose justement d’ajouter #[derive(Debug)], ou d’implémenter Debug à la main.
{} demande Display. Rust ne peut pas le dériver, parce que vous seul savez à quoi doit ressembler la version « jolie ». Écrivez un petit impl avec write!. Servez-vous-en pour le texte que vos utilisateurs verront.
use std::fmt;
#[derive(Debug)] // vous donne {:?} gratuitementstruct Player { name: String, score: u32,}
// Display, c'est vous qui l'écrivez : il décide de ce qu'affiche {}.impl fmt::Display for Player { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { write!(f, "{} ({} points)", self.name, self.score) }}
fn main() { let p = Player { name: String::from("Ana"), score: 42 }; println!("{:?}", p); println!("{}", p);}Sortie :
Player { name: "Ana", score: 42 }Ana (42 points)Solution 2 : comparer, trier, ou utiliser comme clé de HashMap
Section intitulée « Solution 2 : comparer, trier, ou utiliser comme clé de HashMap ».contains() et == demandent PartialEq. .sort() demande Ord. Les clés de HashSet et de HashMap demandent Eq et Hash. Les messages ressemblent à ceci :
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`Autrement dit : « impossible de comparer Point avec Point », « Priority n’implémente pas Ord », « Point n’implémente pas Hash ». La correction consiste à les dériver. Ils s’appuient les uns sur les autres, donc l’ensemble complet habituel est 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(); // demande Ord println!("{:?}", tasks); println!("Contient High : {}", tasks.contains(&Priority::High)); // demande PartialEq
let mut counts: HashMap<Priority, u32> = HashMap::new(); // la clé demande Eq + Hash for task in &tasks { *counts.entry(*task).or_insert(0) += 1; } println!("Tâches Low : {}", counts[&Priority::Low]);}Sortie :
[Low, Medium, High]Contient High : trueTâches Low : 1Un Ord dérivé trie les variantes d’un enum dans l’ordre où vous les avez écrites, et les structs champ par champ, de haut en bas. Un piège, pour être honnête : un type qui a un champ f64 ne peut pas dériver Eq, Hash ni Ord, parce que les flottants ont NaN, qui n’est pas égal à lui-même. Pour trier des flottants, voir Le tri.
Solution 3 : mélanger des types de nombres (cannot add f64 to i32)
Section intitulée « Solution 3 : mélanger des types de nombres (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`Le message dit « impossible d’ajouter un f64 à un i32 » : il n’existe pas d’implémentation de i32 + f64. Rust ne convertit jamais les nombres à votre place, même pas de i32 vers f64. Les deux côtés de +, -, * et / doivent avoir le même type. Convertissez vous-même un des deux côtés avec as :
fn main() { let items: i32 = 3; let price: f64 = 2.5; let total = items as f64 * price; // on convertit d'abord, puis on multiplie println!("Total : {total}");}Sortie :
Total : 7.5f64::from(items) marche aussi et ne peut jamais perdre de données. Dans l’autre sens (de f64 vers i32), les décimales sont coupées, alors pensez d’abord à arrondir. Les pages Conversion de types et Les conversions donnent tous les détails.
Solution 4 : ? couldn’t convert the error
Section intitulée « Solution 4 : ? couldn’t convert the error »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` trait« ? n’a pas pu convertir l’erreur en ConfigError ». Quand ? tombe sur une erreur, il la renvoie depuis votre fonction. Si le type de cette erreur n’est pas celui que votre fonction promet, ? essaie de la convertir avec From (c’est ce que dit la note). À vous d’écrire ce From :
use std::num::ParseIntError;
#[derive(Debug)]enum ConfigError { BadPort(ParseIntError),}
// On apprend à `?` comment transformer une ParseIntError en 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("quatre-vingts"));}Sortie :
Ok(8080)Err(BadPort(ParseIntError { kind: InvalidDigit }))Autres bonnes options : convertir un seul appel avec .map_err(ConfigError::BadPort)?, utiliser #[from] du crate thiserror, ou renvoyer Result<T, Box<dyn std::error::Error>> dans les petits programmes. Voir Les erreurs personnalisées et anyhow et thiserror.
Solution 5 : Rc cannot be sent between threads safely
Section intitulée « Solution 5 : Rc cannot be sent between threads safely »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>`« Rc<String> ne peut pas être envoyé entre threads sans danger. » Rc compte ses propriétaires avec un simple nombre, que deux threads pourraient modifier en même temps et corrompre. Rc n’est donc pas Send. Utilisez plutôt Arc (la version « atomique ») : il s’utilise de la même façon et ne pose aucun problème entre threads :
use std::sync::Arc;use std::thread;
fn main() { let config = Arc::new(String::from("debug")); let for_thread = Arc::clone(&config); // un deuxième propriétaire pour le thread
let handle = thread::spawn(move || { println!("Le thread voit : {for_thread}"); }); handle.join().unwrap();
println!("main a toujours : {config}");}Sortie :
Le thread voit : debugmain a toujours : debugC’est pareil pour RefCell : entre threads, utilisez Arc<Mutex<T>> au lieu de Rc<RefCell<T>>. Voir L’état partagé et La concurrence.
Où vous le verrez souvent
Section intitulée « Où vous le verrez souvent »- Les fonctions génériques.
fn announce<T: Display>(item: T)appelée avec votre struct donne`Song` doesn't implement `std::fmt::Display`avec « required by a bound inannounce» (exigé par une contrainte deannounce). Correction : implémentez le trait que demande la contrainte. - Les tests.
assert_eq!(a, b)demande à la foisPartialEqetDebug(pour afficher les valeurs quand le test échoue). Correction :#[derive(Debug, PartialEq)]. .collect()vers le mauvais type.let nums: Vec<i32> = words.iter().collect();donne « a value of typeVec<i32>cannot be built from an iterator over elements of type&String» (on ne peut pas construire unVec<i32>à partir d’éléments&String). Correction : convertissez d’abord chaque élément, par exemple avec.map(|w| w.parse().unwrap()). Voir Les itérateurs.
Erreurs liées
Section intitulée « Erreurs liées »- E0599 : no method found : appeler
.insert()sur uneHashMapdont la clé n’est pasHashdonne parfois E0599 (« trait bounds were not satisfied ») à la place. Même correction : dérivez les traits. - E0308 : mismatched types : vous avez donné un type alors qu’un autre était attendu, par exemple un
i32là où il faut unf64. - E0382 : borrow of moved value : apparaît souvent juste après, quand on ajoute
CloneouCopyen pensant aux traits. E0369: binary operation == cannot be applied: la version de l’opérateur==duPartialEqmanquant. Même correction.- Toutes les erreurs du compilateur expliquées
Petit quiz
Section intitulée « Petit quiz »Petit quiz
1. Vous obtenez "Order doesn't implement Debug" en utilisant {:?}. Quelle est la correction la plus rapide ?
2. Pourquoi let total = 3_i32 + 2.5_f64; échoue-t-il ?
3. Votre code avec un thread échoue avec "Rc<String> cannot be sent between threads safely". Que devez-vous utiliser ?
Score : 0 / 3
Pour aller plus loin
Section intitulée « Pour aller plus loin »- E0277 — Rust error codes index (en anglais) : l’explication officielle de cette erreur.
- Traits: Defining Shared Behavior — The Rust Book (en anglais) : ce que sont les traits et comment fonctionnent les contraintes de trait (trait bounds).
- Derivable Traits — The Rust Book, Appendix C (en anglais) : ce que vous apportent
Debug,PartialEq,Eq,Hash,PartialOrdetOrdquand vous les dérivez. - Recoverable Errors with Result — The Rust Book (en anglais) : comment fonctionne
?, y compris sa conversion avecFrom. Send— documentation de la bibliothèque standard (en anglais) : quels types peuvent passer d’un thread à l’autre, et pourquoiRcne le peut pas.