Erreur Rust E0404 : expected trait, found struct or derive macro (pas un trait)
E0404 veut dire : Rust a trouvé le nom que vous avez écrit, mais ce n’est pas un trait, et seul un trait peut aller à cet endroit. Ces endroits sont impl ___ for Type, une contrainte (bound) comme T: ___, et dyn ___ ou impl ___. Le cas le plus courant surprend beaucoup de monde : impl Debug for Point sans import. Debug est bien visible, mais sous la forme de la macro #[derive(Debug)], pas sous la forme du trait. L’autre cas fréquent, c’est une struct utilisée comme si c’était un trait, ce qui arrive souvent quand on a l’habitude des classes et de l’héritage. Les deux se corrigent vite. Cette page fait partie du guide des erreurs du compilateur Rust.
Le message d’erreur
Section intitulée « Le message d’erreur »error[E0404]: expected trait, found derive macro `Debug` --> src/main.rs:6:6 |6 | impl Debug for Point { // ❌ ici, `Debug` est la macro derive, pas le trait | ^^^^^ not a trait |help: consider importing this trait instead |1 + use std::fmt::Debug; |Le compilateur vous dit : « on attendait un trait, mais on a trouvé la macro derive Debug », et l’aide vous propose d’importer plutôt ce trait.
Voici le programme qui la provoque :
struct Point { x: i32, y: i32,}
impl Debug for Point { // ❌ ici, `Debug` est la macro derive, pas le trait fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { write!(f, "Point at ({}, {})", self.x, self.y) }}
fn main() { let p = Point { x: 3, y: 4 }; println!("{:?}", p);}Ce que ça veut dire
Section intitulée « Ce que ça veut dire »Les ^^^^^ pointent Debug à la ligne 6, avec l’étiquette « not a trait » (pas un trait). Le titre dit ce que Rust a trouvé à la place : une macro derive appelée Debug.
C’est parce qu’il existe deux choses qui s’appellent Debug. La macro Debug, que vous utilisez sous la forme #[derive(Debug)], est dans le prélude (prelude), donc chaque fichier la voit. Le trait std::fmt::Debug, dont vous avez besoin pour impl Debug for ..., n’est pas dans le prélude. Rust trouve donc le seul Debug qu’il voit, remarque que c’est une macro, et s’arrête. La ligne d’aide donne l’import qui fait entrer le trait. Hash marche de la même façon : expected trait, found derive macro `Hash` , corrigé avec use std::hash::Hash;.
Quand le titre dit found struct `Animal` (ou enum, ou type alias), le nom est l’un de vos propres types, utilisé là où un trait est attendu.
C’est comme demander « un conducteur avec son permis » et recevoir une voiture. Une voiture, c’est bien réel, et ça a un rapport, mais le travail demandait quelqu’un qui sait conduire : un ensemble de capacités, et c’est exactement ce qu’est un trait.
Pourquoi Rust fait ça
Section intitulée « Pourquoi Rust fait ça »Rust sépare les données du comportement. Une struct ou un enum décrit des données : ce qu’une valeur est. Un trait décrit un comportement : ce qu’un type sait faire. impl Trait for Type relie les deux : « ce type sait faire ces choses ». Une contrainte comme T: Trait veut dire « n’importe quel type qui sait faire ces choses ».
Une struct ne peut pas aller à ces endroits, parce que ce n’est pas une liste de capacités. Et Rust n’a pas d’héritage, donc impl Animal for Dog ne peut pas vouloir dire « Dog est une sorte d’Animal » quand Animal est une struct. Si vous voulez que plusieurs types partagent un comportement, la partie commune est un trait. Voir Les traits et Les génériques.
Rust range aussi les macros et les traits dans des espaces de noms séparés, et c’est pour ça que Debug peut être à la fois une macro et un trait. La macro derive est dans le prélude pour que #[derive(Debug)] marche partout ; le trait n’a besoin d’être nommé que quand vous l’implémentez vous-même ou l’utilisez comme contrainte.
Comment corriger
Section intitulée « Comment corriger »Solution 1 : importer le trait (use std::fmt::Debug;)
Section intitulée « Solution 1 : importer le trait (use std::fmt::Debug;) »Choisissez-la quand le message dit found derive macro et que vous voulez votre propre format, ou que vous avez besoin du trait dans une contrainte comme T: Debug. Ajoutez la ligne proposée par l’aide :
use std::fmt::Debug;
struct Point { x: i32, y: i32,}
impl Debug for Point { fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { write!(f, "Point en ({}, {})", self.x, self.y) }}
fn print_all<T: Debug>(items: &[T]) { for item in items { println!("{:?}", item); }}
fn main() { let p = Point { x: 3, y: 4 }; println!("{:?}", p); print_all(&[Point { x: 0, y: 0 }, Point { x: -1, y: 7 }]);}Sortie :
Point en (3, 4)Point en (0, 0)Point en (-1, 7)Une variante courante : use std::fmt;, puis impl fmt::Debug for Point avec fmt::Formatter et fmt::Result. Les deux sont bien ; choisissez un seul style par fichier.
Solution 2 : utiliser #[derive(Debug)] au lieu d’écrire l’impl
Section intitulée « Solution 2 : utiliser #[derive(Debug)] au lieu d’écrire l’impl »Choisissez-la quand vous n’avez pas besoin d’un format personnalisé, ce qui est le cas la plupart du temps pour Debug. La macro derive écrit tout le impl Debug for Point à votre place, et vous n’avez besoin d’aucun import :
#[derive(Debug)] // ✅ la macro derive écrit `impl Debug for Point` à votre placestruct Point { x: i32, y: i32,}
fn main() { let p = Point { x: 3, y: 4 }; println!("{:?}", p); println!("{:#?}", p); println!("distance depuis (0, 0) sur une grille : {}", p.x.abs() + p.y.abs());}Sortie :
Point { x: 3, y: 4 }Point { x: 3, y: 4,}distance depuis (0, 0) sur une grille : 7Utilisez l’un ou l’autre, pas les deux : #[derive(Debug)] plus votre propre impl Debug donne une erreur « conflicting implementations » (implémentations en conflit, E0119, en anglais). {:#?} est la forme jolie, sur plusieurs lignes. Plus de détails dans Les traits courants.
Solution 3 : définir un trait pour le comportement commun
Section intitulée « Solution 3 : définir un trait pour le comportement commun »Choisissez-la quand le message dit found struct dans une ligne impl X for Y. Vous vouliez sans doute que Dog soit « une sorte d’Animal », comme une sous-classe :
error[E0404]: expected trait, found struct `Animal` --> src/main.rs:7:6 |7 | impl Animal for Dog { // ❌ `Animal` est une struct, pas un trait | ^^^^^^ not a traitIci, rustc dit « on attendait un trait, mais on a trouvé la struct Animal ».
Le programme qui la provoque :
struct Animal { name: String,}
struct Dog;
impl Animal for Dog { // ❌ `Animal` est une struct, pas un trait fn name(&self) -> String { String::from("Rex") }}
fn main() { println!("{}", Dog.name());}En Rust, la partie commune est un trait : une liste de méthodes que chaque type doit fournir, plus des méthodes par défaut facultatives. Les données restent dans chaque struct :
trait Animal { // ✅ un trait : ce que chaque animal sait faire fn name(&self) -> String;
fn greet(&self) -> String { // une méthode par défaut format!("Salut, je suis {} !", self.name()) }}
struct Dog;
struct Cat { name: String, // les données vivent dans la struct}
impl Animal for Dog { fn name(&self) -> String { String::from("Rex") }}
impl Animal for Cat { fn name(&self) -> String { self.name.clone() }}
fn main() { println!("{}", Dog.greet()); let tom = Cat { name: String::from("Tom") }; println!("{}", tom.greet());}Sortie :
Salut, je suis Rex !Salut, je suis Tom !Si vous vouliez seulement ajouter des méthodes à une seule struct, vous n’avez pas besoin de trait du tout : écrivez impl Dog { fn name(&self) -> String { ... } }, sans for. Voir Définir un trait.
Solution 4 : prendre la struct directement, ou mettre une contrainte sur un trait
Section intitulée « Solution 4 : prendre la struct directement, ou mettre une contrainte sur un trait »Choisissez-la quand l’erreur pointe une contrainte comme T: Shape ou impl Shape, et que Shape est votre struct :
error[E0404]: expected trait, found struct `Shape` --> src/main.rs:5:16 |5 | fn describe<T: Shape>(item: &T) { // ❌ `Shape` est une struct, pas un trait | ^^^^^ not a traitLe programme qui la provoque :
struct Shape { sides: u32,}
fn describe<T: Shape>(item: &T) { // ❌ `Shape` est une struct, pas un trait println!("a shape");}
fn main() { describe(&Shape { sides: 3 });}Une contrainte veut dire « n’importe quel type qui implémente ce trait ». Si la fonction ne reçoit jamais qu’une Shape, retirez le générique et prenez &Shape. Si elle doit marcher pour beaucoup de types, mettez le comportement commun dans un trait et utilisez ce trait comme contrainte :
struct Shape { sides: u32,}
fn describe(item: &Shape) { // ✅ on prend la struct elle-même, pas besoin de générique println!("une forme à {} côtés", item.sides);}
trait Named { // ✅ ou un trait, pour « n'importe quel type qui a un nom » fn name(&self) -> String;}
impl Named for Shape { fn name(&self) -> String { match self.sides { 3 => String::from("triangle"), 4 => String::from("carré"), n => format!("polygone à {} côtés", n), } }}
fn shout<T: Named>(item: &T) { // ✅ une contrainte de trait a besoin d'un trait println!("{} !", item.name().to_uppercase());}
fn main() { let t = Shape { sides: 3 }; describe(&t); shout(&t); shout(&Shape { sides: 6 });}Sortie :
une forme à 3 côtésTRIANGLE !POLYGONE À 6 CÔTÉS !Les contraintes de trait sont expliquées dans Les génériques et Les traits.
Où vous le verrez souvent
Section intitulée « Où vous le verrez souvent »impl Vec<i32> comme type de paramètre. fn total(x: impl Vec<i32>) donne expected trait, found struct `Vec` . À cet endroit, impl a besoin d’un trait. Correction : prenez &[i32] ou Vec<i32>, ou impl IntoIterator<Item = i32> si vous voulez accepter n’importe quelle collection.
Box<dyn String>. dyn a aussi besoin d’un trait, donc ça donne expected trait, found struct `String` , et rustc propose ToString. Correction : Box<dyn ToString> si vous vouliez dire « tout ce qui peut devenir une chaîne », ou simplement String. Voir Les trait objects.
Un alias de type pour un trait. type Printable = dyn std::fmt::Display; suivi de impl Printable for A donne expected trait, found type alias `Printable` avec l’étiquette « type aliases cannot be used as traits » (les alias de type ne peuvent pas servir de traits). Correction : écrivez le vrai nom du trait dans le impl.
Erreurs liées
Section intitulée « Erreurs liées »- E0405 : cannot find trait in this scope : le nom n’existe pas du tout, comme
Displaysans import. E0404 veut dire que le nom existe, mais que ce n’est pas la bonne sorte de chose. - E0277 : the trait bound is not satisfied : le trait est le bon, mais le type ne l’implémente pas.
- E0425 : cannot find value in this scope : le même genre d’erreur « introuvable » pour les valeurs, les fonctions et les types.
- E0107 : wrong number of generic arguments : le trait ou le type est le bon, mais il a trop ou trop peu d’arguments
<...>. - E0119 : conflicting implementations (en anglais) :
#[derive(Debug)]et votre propreimpl Debugsur le même type. - Toutes les erreurs du compilateur : la liste complète des pages d’erreurs.
Petit quiz
Section intitulée « Petit quiz »Petit quiz
1. impl Debug for Point donne « expected trait, found derive macro Debug ». Quelle est la correction la plus simple si vous voulez un format personnalisé ?
2. Vous avez struct Animal et vous écrivez impl Animal for Dog. Que veut Rust à la place ?
3. Lequel de ces éléments peut aller après T: dans fn show<T: ___>(x: T) ?
Score : 0 / 3
Pour aller plus loin
Section intitulée « Pour aller plus loin »- E0404 — Rust error codes index (en anglais) : l’explication officielle, avec l’exemple d’une struct utilisée comme trait.
- Defining Shared Behavior with Traits — The Rust Book (en anglais) : définir un trait, l’implémenter et l’utiliser comme contrainte.
std::fmt::Debug— standard library docs (en anglais) : dériverDebugou l’implémenter à la main.- Traits — Rust by Example (en anglais) : un trait partagé par plusieurs types, avec des méthodes par défaut.
std::prelude— standard library docs (en anglais) : ce qui est visible dans chaque fichier, dont la macro deriveDebug, mais pas le trait.