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).
El mensaje de error
Sección titulada «El mensaje de error»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) insteadEl 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);}Qué significa
Sección titulada «Qué significa»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.
Por qué Rust hace esto
Sección titulada «Por qué Rust hace esto»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.
Cómo solucionarlo
Sección titulada «Cómo solucionarlo»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 {:?} gratisstruct 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)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?: trueTareas de prioridad baja: 1Un 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.5f64::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.
Solución 4: ? no pudo convertir el error
Sección titulada «Solución 4: ? no pudo convertir el error»error[E0277]: `?` couldn't convert the error to `ConfigError` |8 | fn parse_port(text: &str) -> Result<u16, ConfigError> { | ------------------------ expected `ConfigError` because of this9 | 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` traitEl 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 }))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: debugmain todavía tiene: debugCon RefCell pasa lo mismo: entre hilos, usa Arc<Mutex<T>> en lugar de Rc<RefCell<T>>. Mira Estado compartido y Concurrencia.
Dónde lo verás normalmente
Sección titulada «Dónde lo verás normalmente»- 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 inannounce” (lo exige una restricción deannounce). Solución: implementa el trait que pide la restricción. - Tests.
assert_eq!(a, b)necesitaPartialEqy tambiénDebug(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 typeVec<i32>cannot be built from an iterator over elements of type&String” (no se puede construir unVec<i32>a partir de elementos&String). Solución: convierte cada elemento antes, por ejemplo con.map(|w| w.parse().unwrap()). Mira Iteradores.
Errores relacionados
Sección titulada «Errores relacionados»- E0599: no method found (método no encontrado): llamar a
.insert()en unHashMapcuya clave no esHasha veces aparece como E0599 (“trait bounds were not satisfied”) en lugar de E0277. La solución es la misma: deriva los traits. - E0308: mismatched types (tipos que no coinciden): diste un tipo donde se esperaba otro, como un
i32donde va unf64. - E0382: borrow of moved value (valor movido): suele aparecer justo al lado, cuando empiezas a añadir
CloneoCopypensando en traits. - E0369: binary operation
==cannot be applied: la versión del operador==cuando faltaPartialEq. La solución es la misma. - Todos los errores del compilador explicados
Comprueba lo que aprendiste
Sección titulada «Comprueba lo que aprendiste»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
Para leer más
Sección titulada «Para leer más»- E0277 — índice de códigos de error de Rust (en inglés): la explicación oficial de este error.
- Traits: definir comportamiento compartido — El libro de Rust (en inglés): qué son los traits y cómo funcionan las restricciones de trait (trait bounds).
- Traits derivables — El libro de Rust (apéndice C) (en inglés): qué te da cada uno de
Debug,PartialEq,Eq,Hash,PartialOrdyOrdal derivarlo. - Errores recuperables con Result — El libro de Rust (en inglés): cómo funciona
?, incluida su conversión conFrom. Send— biblioteca estándar de Rust (en inglés): qué tipos pueden pasar de un hilo a otro, y por quéRcno puede.