Ir al contenido

Error E0277 de Rust: the trait bound is not satisfied (trait no implementado)

El error E0277 significa: usaste un tipo en un sitio que necesita una capacidad que ese tipo no tiene. Imprimirlo, compararlo, sumarlo, convertirlo, enviarlo a otro hilo: cada una de estas cosas necesita un trait (rasgo), y a tu tipo le falta ese trait. Es uno de los errores más comunes de Rust, el mensaje suele decirte la solución exacta y, la mayoría de las veces, la solución es una sola línea. (Esta página forma parte de Errores del compilador de Rust explicados).

La versión que casi todo el mundo se encuentra primero es al imprimir su propia struct con {}:

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

El compilador dice que Player no implementa Display, así que no sabe imprimirlo con {}, y te sugiere probar con {:?}.

El programa que lo provoca:

struct Player {
name: String,
score: u32,
}
fn main() {
let p = Player { name: String::from("Ana"), score: 42 };
println!("{}", p);
}

Un trait es una promesa de que un tipo sabe hacer algo. Display es la promesa “sé imprimirme de forma bonita”. PartialEq es “me pueden comparar con ==”. Send es “es seguro moverme a otro hilo”.

Código como println!, .sort(), + y thread::spawn pide una de esas promesas. El compilador señala el valor que pasaste (p en la línea 8) y dice: este tipo nunca hizo esa promesa, así que no puedo usarlo aquí.

Piensa en un enchufe. El enchufe solo acepta clavijas de una forma. Tu aparato puede funcionar perfectamente, pero si su clavija no encaja, no entra. E0277 es el compilador diciendo “esa clavija no encaja”. Lo arreglas dándole al tipo la clavija correcta: normalmente con #[derive(...)] y a veces con un bloque impl corto.

Rust nunca adivina. Para tu propio tipo Player no existe una única forma “correcta” de imprimirlo, compararlo u ordenarlo. ¿Dos jugadores son iguales cuando coinciden sus nombres, o solo cuando coinciden todos los campos? Rust te pide que lo digas tú, en lugar de hacer en silencio algo que no querías.

La comprobación ocurre al compilar. En muchos lenguajes, imprimir un objeto al que nunca enseñaste a imprimirse te da algo inútil como <Player object at 0x7f...>, y enviar a un hilo un valor que no es seguro entre hilos te da un fallo raro en producción. En Rust, las dos cosas son errores de compilación que arreglas una sola vez.

Puedes leer más sobre cómo funcionan los traits en la página de Traits, y sobre los de todos los días (Debug, Display, PartialEq, Ord, Hash) en Traits comunes.

E0277 tiene muchas caras. Busca abajo el mensaje que se parece al tuyo.

Solución 1: Imprimir una struct con {} o {:?}

Sección titulada «Solución 1: Imprimir una struct con {} o {:?}»

{:?} necesita Debug. Casi nunca lo escribes a mano: añade #[derive(Debug)]. Úsalo para depurar y para los logs. La versión del error con {:?} es así:

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`

Aquí la nota ya te da la solución: añadir #[derive(Debug)] a Player o escribir impl Debug a mano.

{} necesita Display. Rust no puede derivarlo, porque solo tú sabes cómo debe verse la versión “bonita”. Escribe un impl pequeño con write!. Úsalo para el texto que verán tus usuarios.

use std::fmt;
#[derive(Debug)] // te da {:?} gratis
struct Player {
name: String,
score: u32,
}
// Display lo escribes tú: esto decide lo que imprime {}.
impl fmt::Display for Player {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "{} ({} puntos)", self.name, self.score)
}
}
fn main() {
let p = Player { name: String::from("Ana"), score: 42 };
println!("{:?}", p);
println!("{}", p);
}

Salida:

Player { name: "Ana", score: 42 }
Ana (42 puntos)
▶ Pruébalo tú mismo

Solución 2: Comparar, ordenar o usar como clave de un HashMap

Sección titulada «Solución 2: Comparar, ordenar o usar como clave de un HashMap»

.contains() y == necesitan PartialEq. .sort() necesita Ord. Las claves de HashSet y HashMap necesitan Eq y Hash. Los mensajes son así:

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`

Son tres formas del mismo problema: “no puedo comparar Point con Point” y “no se cumple el trait Ord (o Hash)”, porque el tipo no los implementa.

La solución es derivarlos. Se apoyan unos en otros, así que el conjunto completo habitual es 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(); // necesita Ord
println!("{:?}", tasks);
println!("¿Hay alguna alta?: {}", tasks.contains(&Priority::High)); // necesita PartialEq
let mut counts: HashMap<Priority, u32> = HashMap::new(); // la clave necesita Eq + Hash
for task in &tasks {
*counts.entry(*task).or_insert(0) += 1;
}
println!("Tareas de prioridad baja: {}", counts[&Priority::Low]);
}

Salida:

[Low, Medium, High]
¿Hay alguna alta?: true
Tareas de prioridad baja: 1
▶ Pruébalo tú mismo

Un Ord derivado ordena las variantes de un enum en el orden en que las escribiste, y las structs campo por campo, de arriba abajo. Una pega, para ser sinceros: un tipo con un campo f64 no puede derivar Eq, Hash ni Ord, porque los números de coma flotante tienen NaN, que no es igual ni a sí mismo. Para ordenar números con decimales, mira Ordenar y buscar.

Solución 3: Mezclar tipos numéricos (cannot add f64 to i32)

Sección titulada «Solución 3: Mezclar tipos numéricos (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`

El compilador dice que no puede sumar un f64 a un i32: no existe una implementación de i32 + f64.

Rust nunca convierte los números por ti, ni siquiera de i32 a f64. Los dos lados de +, -, * y / deben ser del mismo tipo. Convierte tú uno de los lados con as:

fn main() {
let items: i32 = 3;
let price: f64 = 2.5;
let total = items as f64 * price; // primero convierte, luego multiplica
println!("Total: {total}");
}

Salida:

Total: 7.5
▶ Pruébalo tú mismo

f64::from(items) también funciona y nunca puede perder datos. En el otro sentido (de f64 a i32) se cortan los decimales, así que piensa primero en cómo redondear. Las páginas de Conversión de tipos y Conversiones cuentan los detalles.

error[E0277]: `?` couldn't convert the error to `ConfigError`
|
8 | fn parse_port(text: &str) -> Result<u16, ConfigError> {
| ------------------------ expected `ConfigError` because of this
9 | 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

El mensaje dice que ? no pudo convertir el error en ConfigError, porque ConfigError no implementa From<ParseIntError>. La nota recuerda que ? convierte los errores usando el trait From.

Cuando ? se encuentra un error, lo devuelve desde tu función. Si el tipo de error es distinto del que promete tu función, ? intenta convertirlo con From. Tu trabajo es escribir ese From:

use std::num::ParseIntError;
#[derive(Debug)]
enum ConfigError {
BadPort(ParseIntError),
}
// Enseña a `?` a convertir un ParseIntError en un 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("ochenta"));
}

Salida:

Ok(8080)
Err(BadPort(ParseIntError { kind: InvalidDigit }))
▶ Pruébalo tú mismo

Otras buenas opciones: convertir solo una llamada con .map_err(ConfigError::BadPort)?, usar #[from] del crate thiserror, o devolver Result<T, Box<dyn std::error::Error>> en programas pequeños. Mira Errores personalizados y anyhow y thiserror.

Solución 5: Rc no se puede enviar a otro hilo de forma segura

Sección titulada «Solución 5: Rc no se puede enviar a otro hilo de forma segura»
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>`

El compilador dice que un Rc<String> no se puede enviar entre hilos de forma segura, porque Rc<String> no implementa Send.

Rc cuenta sus dueños con un número normal, y dos hilos podrían cambiarlo a la vez y estropearlo. Por eso Rc no es Send. Usa Arc (la versión “atómica”) en su lugar. Funciona igual y es seguro entre hilos:

use std::sync::Arc;
use std::thread;
fn main() {
let config = Arc::new(String::from("debug"));
let for_thread = Arc::clone(&config); // un segundo dueño para el hilo
let handle = thread::spawn(move || {
println!("El hilo ve: {for_thread}");
});
handle.join().unwrap();
println!("main todavía tiene: {config}");
}

Salida:

El hilo ve: debug
main todavía tiene: debug
▶ Pruébalo tú mismo

Con RefCell pasa lo mismo: entre hilos, usa Arc<Mutex<T>> en lugar de Rc<RefCell<T>>. Mira Estado compartido y Concurrencia.

  • Funciones genéricas. Si llamas a fn announce<T: Display>(item: T) con tu struct, obtienes `Song` doesn't implement `std::fmt::Display` con “required by a bound in announce” (lo exige una restricción de announce). Solución: implementa el trait que pide la restricción.
  • Tests. assert_eq!(a, b) necesita PartialEq y también Debug (para imprimir los valores cuando el test falla). Solución: #[derive(Debug, PartialEq)].
  • .collect() en el tipo equivocado. let nums: Vec<i32> = words.iter().collect(); da “a value of type Vec<i32> cannot be built from an iterator over elements of type &String” (no se puede construir un Vec<i32> a partir de elementos &String). Solución: convierte cada elemento antes, por ejemplo con .map(|w| w.parse().unwrap()). Mira Iteradores.

Comprueba lo aprendido

1. Al usar {:?} te sale "Order doesn't implement Debug". ¿Cuál es la solución más rápida?

2. ¿Por qué falla let total = 3_i32 + 2.5_f64;?

3. Tu código con hilos falla con "Rc<String> cannot be sent between threads safely". ¿Qué deberías usar?

Puntuación: 0 / 3