Error E0404 de Rust: expected trait, found derive macro (se esperaba un trait)
E0404 significa: Rust encontró el nombre que escribiste, pero no es un trait, y en ese sitio solo puede ir un trait. Esos sitios son impl ___ for Tipo, una restricción como T: ___, y dyn ___ o impl ___. El caso más común sorprende a mucha gente: impl Debug for Point sin ningún import. Debug sí está en el ámbito, pero como la macro #[derive(Debug)], no como el trait. El otro caso habitual es un struct usado como si fuera un trait, algo que pasa mucho cuando vienes de lenguajes con clases y herencia. Los dos se arreglan rápido. (Esta página forma parte de Errores del compilador de Rust explicados.)
El mensaje de error
Sección titulada «El mensaje de error»error[E0404]: expected trait, found derive macro `Debug` --> src/main.rs:6:6 |6 | impl Debug for Point { // ❌ `Debug` here is the derive macro, not the trait | ^^^^^ not a trait |help: consider importing this trait instead |1 + use std::fmt::Debug; |En español: “se esperaba un trait, pero se encontró la macro derive Debug”. El help te sugiere importar el trait.
Este es el programa que lo produce:
struct Point { x: i32, y: i32,}
impl Debug for Point { // ❌ `Debug` here is the derive macro, not the 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);}Qué significa
Sección titulada «Qué significa»El ^^^^^ señala Debug en la línea 6, con la etiqueta “not a trait” (no es un trait). El titular dice qué encontró Rust en su lugar: una macro derive llamada Debug.
Eso pasa porque hay dos cosas que se llaman Debug. La macro Debug, la que usas como #[derive(Debug)], está en el prelude (los nombres que cada archivo recibe gratis), así que todos los archivos la ven. El trait std::fmt::Debug, que es el que necesitas para impl Debug for ..., no está en el prelude. Así que Rust encuentra el único Debug que ve, se da cuenta de que es una macro y se detiene. La línea del help te da el import que trae el trait. Con Hash pasa lo mismo: expected trait, found derive macro `Hash` , que se arregla con use std::hash::Hash;.
Cuando el titular dice found struct `Animal` (o enum, o type alias), el nombre es uno de tus propios tipos, usado donde va un trait.
Es como pedir “alguien con carnet de conducir” y que te den un coche. El coche es algo real, y está muy relacionado, pero el trabajo pedía a alguien que sepa conducir: un conjunto de habilidades, que es justo lo que es un trait.
Por qué Rust hace esto
Sección titulada «Por qué Rust hace esto»Rust separa los datos del comportamiento. Un struct o un enum describe datos: lo que un valor es. Un trait describe comportamiento: lo que un tipo sabe hacer. impl Trait for Tipo conecta las dos cosas: “este tipo sabe hacer estas cosas”. Una restricción como T: Trait significa “cualquier tipo que sepa hacer estas cosas”.
Un struct no puede ir en esos sitios, porque no es una lista de habilidades. Y Rust no tiene herencia, así que impl Animal for Dog no puede significar “Dog es un tipo de Animal” cuando Animal es un struct. Si quieres que varios tipos compartan comportamiento, la parte compartida es un trait. Mira Traits y Genéricos.
Además, Rust guarda las macros y los traits en espacios de nombres distintos, y por eso Debug puede ser a la vez una macro y un trait. La macro derive está en el prelude para que #[derive(Debug)] funcione en todas partes; el trait solo hace falta nombrarlo cuando lo implementas tú o lo usas en una restricción.
Cómo solucionarlo
Sección titulada «Cómo solucionarlo»Solución 1: Importa el trait (use std::fmt::Debug;)
Sección titulada «Solución 1: Importa el trait (use std::fmt::Debug;)»Elige esta cuando el mensaje dice found derive macro y quieres tu propio formato, o necesitas el trait en una restricción como T: Debug. Añade la línea del help:
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, "Punto 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 }]);}Salida:
Punto en (3, 4)Punto en (0, 0)Punto en (-1, 7)Una alternativa habitual es use std::fmt; y luego impl fmt::Debug for Point con fmt::Formatter y fmt::Result. Las dos están bien; elige un estilo por archivo.
Solución 2: Usa #[derive(Debug)] en lugar de escribir el impl
Sección titulada «Solución 2: Usa #[derive(Debug)] en lugar de escribir el impl»Elige esta cuando no necesitas un formato propio, que con Debug es casi siempre. La macro derive escribe todo el impl Debug for Point por ti, y no necesitas ningún import:
#[derive(Debug)] // ✅ la macro derive escribe `impl Debug for Point` por tistruct Point { x: i32, y: i32,}
fn main() { let p = Point { x: 3, y: 4 }; println!("{:?}", p); println!("{:#?}", p); println!("distancia desde (0, 0) en una cuadrícula: {}", p.x.abs() + p.y.abs());}Salida:
Point { x: 3, y: 4 }Point { x: 3, y: 4,}distancia desde (0, 0) en una cuadrícula: 7Usa una cosa u otra, no las dos: #[derive(Debug)] más tu propio impl Debug da un error de “conflicting implementations” (implementaciones en conflicto, E0119, en inglés). {:#?} es la forma bonita, en varias líneas. Más en Traits comunes.
Solución 3: Define un trait para el comportamiento compartido
Sección titulada «Solución 3: Define un trait para el comportamiento compartido»Elige esta cuando el mensaje dice found struct en una línea impl X for Y. Seguramente querías que Dog fuera “un tipo de Animal”, como una subclase:
error[E0404]: expected trait, found struct `Animal` --> src/main.rs:7:6 |7 | impl Animal for Dog { // ❌ `Animal` is a struct, not a trait | ^^^^^^ not a traitEl programa que lo produce:
struct Animal { name: String,}
struct Dog;
impl Animal for Dog { // ❌ `Animal` is a struct, not a trait fn name(&self) -> String { String::from("Rex") }}
fn main() { println!("{}", Dog.name());}En Rust, la parte compartida es un trait: una lista de métodos que cada tipo tiene que aportar, más métodos por defecto opcionales. Los datos se quedan en cada struct:
trait Animal { // ✅ un trait: lo que todo animal sabe hacer fn name(&self) -> String;
fn greet(&self) -> String { // un método por defecto format!("¡Hola, soy {}!", self.name()) }}
struct Dog;
struct Cat { name: String, // los datos viven en el 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());}Salida:
¡Hola, soy Rex!¡Hola, soy Tom!Si solo querías añadir métodos a un struct, no necesitas ningún trait: escribe impl Dog { fn name(&self) -> String { ... } }, sin for. Mira Definir un trait.
Solución 4: Recibe el struct directamente, o pon la restricción sobre un trait
Sección titulada «Solución 4: Recibe el struct directamente, o pon la restricción sobre un trait»Elige esta cuando el error señala una restricción como T: Shape o impl Shape, y Shape es tu struct:
error[E0404]: expected trait, found struct `Shape` --> src/main.rs:5:16 |5 | fn describe<T: Shape>(item: &T) { // ❌ `Shape` is a struct, not a trait | ^^^^^ not a traitEl programa que lo produce:
struct Shape { sides: u32,}
fn describe<T: Shape>(item: &T) { // ❌ `Shape` is a struct, not a trait println!("a shape");}
fn main() { describe(&Shape { sides: 3 });}Una restricción dice “cualquier tipo que implemente este trait”. Si la función solo va a recibir un Shape, quita el genérico y recibe &Shape. Si tiene que funcionar con muchos tipos, pon el comportamiento compartido en un trait y usa ese trait en la restricción:
struct Shape { sides: u32,}
fn describe(item: &Shape) { // ✅ recibe el propio struct, sin genérico println!("una figura con {} lados", item.sides);}
trait Named { // ✅ o un trait, para "cualquier tipo que tenga nombre" fn name(&self) -> String;}
impl Named for Shape { fn name(&self) -> String { match self.sides { 3 => String::from("triángulo"), 4 => String::from("cuadrado"), n => format!("polígono de {} lados", n), } }}
fn shout<T: Named>(item: &T) { // ✅ una restricción de trait necesita un trait println!("¡{}!", item.name().to_uppercase());}
fn main() { let t = Shape { sides: 3 }; describe(&t); shout(&t); shout(&Shape { sides: 6 });}Salida:
una figura con 3 lados¡TRIÁNGULO!¡POLÍGONO DE 6 LADOS!Las restricciones de traits (trait bounds) se explican en Genéricos y Traits.
Dónde lo verás normalmente
Sección titulada «Dónde lo verás normalmente»impl Vec<i32> como tipo de un parámetro. fn total(x: impl Vec<i32>) da expected trait, found struct `Vec` . En ese sitio, impl necesita un trait. Solución: recibe &[i32] o Vec<i32>, o impl IntoIterator<Item = i32> si quieres aceptar cualquier colección.
Box<dyn String>. dyn también necesita un trait, así que esto da expected trait, found struct `String` , y rustc te sugiere ToString. Solución: Box<dyn ToString> si querías decir “cualquier cosa que se pueda convertir en texto”, o simplemente String. Mira Trait objects.
Un alias de tipo para un trait. type Printable = dyn std::fmt::Display; seguido de impl Printable for A da expected trait, found type alias `Printable` con la etiqueta “type aliases cannot be used as traits” (los alias de tipo no se pueden usar como traits). Solución: escribe el nombre real del trait en el impl.
Errores relacionados
Sección titulada «Errores relacionados»- E0405: cannot find trait in this scope: el nombre no existe en absoluto, como
Displaysin import. E0404 significa que el nombre existe, pero es de otro tipo. - E0277: the trait bound is not satisfied: el trait es el correcto, pero el tipo no lo implementa.
- E0425: cannot find value in this scope: el mismo tipo de error de “no encontrado”, para valores, funciones y tipos.
- E0107: wrong number of generic arguments: el trait o el tipo son correctos, pero tienen demasiados o demasiado pocos argumentos entre
<...>. - E0119: conflicting implementations (en inglés):
#[derive(Debug)]y tu propioimpl Debugen el mismo tipo. - Todos los errores del compilador explicados
Comprueba lo que aprendiste
Sección titulada «Comprueba lo que aprendiste»Comprueba lo aprendido
1. impl Debug for Point da "expected trait, found derive macro Debug". ¿Cuál es la solución más sencilla si quieres un formato propio?
2. Tienes struct Animal y escribes impl Animal for Dog. ¿Qué quiere Rust en su lugar?
3. ¿Cuál de estos puede ir detrás de T: en fn show<T: ___>(x: T)?
Puntuación: 0 / 3
Para leer más
Sección titulada «Para leer más»- E0404 — Rust error codes index (en inglés): la explicación oficial, con el ejemplo del struct usado como trait.
- Defining Shared Behavior with Traits — El libro de Rust (en inglés): definir un trait, implementarlo y usarlo como restricción. También existe una traducción al español de la comunidad del libro.
std::fmt::Debug— documentación de la biblioteca estándar (en inglés): derivarDebugo implementarlo a mano.- Traits — Rust by Example (en inglés): un trait compartido por varios tipos, con métodos por defecto.
std::prelude— documentación de la biblioteca estándar (en inglés): lo que está en el ámbito en cada archivo, incluida la macro deriveDebug, pero no el trait.