Aller au contenu

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.

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

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.

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.

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)
▶ Essayez par vous-même

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 place
struct 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 : 7
▶ Essayez par vous-même

Utilisez 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 trait

Ici, 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 !
▶ Essayez par vous-même

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 trait

Le 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és
TRIANGLE !
POLYGONE À 6 CÔTÉS !
▶ Essayez par vous-même

Les contraintes de trait sont expliquées dans Les génériques et Les traits.

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.

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