La stdlib de Rust define decenas de traits, pero hay un conjunto reducido que aparece en casi todo el código. Implementarlos correctamente hace que tus tipos se integren con el ecosistema: se pueden imprimir, comparar, ordenar, convertir y usar como claves en mapas. Esta guía cubre los más importantes con ejemplos y errores reales del compilador.
Display: formato para el usuario
use std::fmt;
struct Punto { x: f64, y: f64 }
impl fmt::Display for Punto {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "({}, {})", self.x, self.y)
}
}
fn main() {
let p = Punto { x: 1.0, y: 2.0 };
println!("{}", p); // (1, 2)
let s = p.to_string(); // to_string() se implementa automáticamente
println!("{}", s);
}
Debug: formato para el programador
#[derive(Debug)]
struct Punto { x: f64, y: f64 }
fn main() {
let p = Punto { x: 1.0, y: 2.0 };
println!("{:?}", p); // Punto { x: 1.0, y: 2.0 }
println!("{:#?}", p); // Pretty-printed
}
Casi siempre basta con #[derive(Debug)]. Solo implementas manualmente si quieres controlar el formato de depuración.
From e Into: conversiones sin pérdida
struct Email(String);
impl From<String> for Email {
fn from(s: String) -> Self {
Email(s)
}
}
fn main() {
let e1 = Email::from(String::from("[email protected]"));
// Into se implementa automáticamente cuando implementas From
let e2: Email = String::from("[email protected]").into();
println!("{}", e1.0);
println!("{}", e2.0);
}
La regla: implementa siempre From, no Into directamente. Into se genera automáticamente a partir de From.
PartialEq y Eq: igualdad
#[derive(Debug, PartialEq)]
struct Punto { x: f64, y: f64 }
fn main() {
let a = Punto { x: 1.0, y: 2.0 };
let b = Punto { x: 1.0, y: 2.0 };
println!("{}", a == b); // true
println!("{}", a != b); // false
}
PartialEq permite usar == y !=. Eq (que extiende PartialEq) indica que la igualdad es total (reflexiva, simétrica, transitiva). f64 implementa PartialEq pero no Eq porque NaN != NaN.
PartialOrd y Ord: ordenación
#[derive(Debug, PartialEq, Eq, PartialOrd, Ord)]
struct Version {
mayor: u32,
menor: u32,
patch: u32,
}
fn main() {
let mut versiones = vec![
Version { mayor: 1, menor: 2, patch: 0 },
Version { mayor: 2, menor: 0, patch: 0 },
Version { mayor: 1, menor: 0, patch: 5 },
];
versiones.sort();
println!("{:?}", versiones);
// Ordena: 1.0.5, 1.2.0, 2.0.0
}
Default: valores iniciales
#[derive(Default, Debug)]
struct Config {
debug: bool, // false
nivel: u32, // 0
nombre: String, // ""
timeout: u64, // 0
}
fn main() {
let cfg = Config::default();
println!("{:?}", cfg);
// Usar ..Default::default() con struct update syntax
let cfg2 = Config {
debug: true,
nombre: String::from("app"),
..Default::default()
};
println!("{:?}", cfg2);
}
Resumen de errores comunes
// Error: no implementa Display
struct MiTipo;
println!("{}", MiTipo); // error[E0277]: `MiTipo` doesn't implement `std::fmt::Display`
// Solución: implementar Display o usar Debug con {:?}
#[derive(Debug)]
struct MiTipo;
println!("{:?}", MiTipo);
Resumen
Display: formato legible por el usuario. Se usa con{}.Debug: formato de depuración. Se usa con{:?}. Casi siempre derive.From<T>: conversión desde otro tipo.Into<T>se genera automáticamente.PartialEq/Eq: igualdad con==y!=.PartialOrd/Ord: ordenación con<,>,sort().Default: valor por defecto conType::default().
El siguiente artículo explora los closures: funciones anónimas que capturan el entorno, los tres modos de captura y los traits Fn, FnMut y FnOnce.
