Ir al contenido

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.)

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

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.

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.

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)
▶ Pruébalo tú mismo

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 ti
struct 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: 7
▶ Pruébalo tú mismo

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

El 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!
▶ Pruébalo tú mismo

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 trait

El 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!
▶ Pruébalo tú mismo

Las restricciones de traits (trait bounds) se explican en Genéricos y Traits.

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.

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