En esta página
Traits: abstracción y comportamiento compartido
Traits: comportamiento compartido
Un trait define un conjunto de métodos que un tipo puede implementar. Son similares a las interfaces en otros lenguajes, pero más poderosos: permiten implementaciones por defecto, pueden tener bounds sobre tipos genéricos, y se usan extensamente en toda la biblioteca estándar.
Definir e implementar traits
trait Saludable {
// Método que el tipo DEBE implementar
fn saludar(&self) -> String;
// Método con implementación por defecto
fn despedirse(&self) -> String {
format!("¡Hasta luego, {}!", self.nombre())
}
// Otro método abstracto
fn nombre(&self) -> &str;
}
struct Persona {
nombre: String,
idioma: String,
}
struct Robot {
id: u32,
modelo: String,
}
impl Saludable for Persona {
fn saludar(&self) -> String {
match self.idioma.as_str() {
"es" => format!("¡Hola! Soy {}", self.nombre),
"en" => format!("Hello! I'm {}", self.nombre),
_ => format!("Hi! I'm {}", self.nombre),
}
}
fn nombre(&self) -> &str {
&self.nombre
}
}
impl Saludable for Robot {
fn saludar(&self) -> String {
format!("ROBOT-{} ({}) ONLINE. PROCESANDO...", self.id, self.modelo)
}
fn nombre(&self) -> &str {
&self.modelo
}
// Sobrescribir el método por defecto
fn despedirse(&self) -> String {
format!("ROBOT-{} OFFLINE.", self.id)
}
}
fn main() {
let persona = Persona {
nombre: String::from("Ana"),
idioma: String::from("es"),
};
let robot = Robot { id: 42, modelo: String::from("T-800") };
println!("{}", persona.saludar());
println!("{}", persona.despedirse()); // Método por defecto
println!("{}", robot.saludar());
println!("{}", robot.despedirse()); // Implementación sobrescrita
}Traits como parámetros
Existen dos sintaxis para usar traits en parámetros de función:
trait Area {
fn area(&self) -> f64;
}
struct Cuadrado { lado: f64 }
struct Circulo { radio: f64 }
impl Area for Cuadrado {
fn area(&self) -> f64 { self.lado * self.lado }
}
impl Area for Circulo {
fn area(&self) -> f64 { std::f64::consts::PI * self.radio * self.radio }
}
// Sintaxis 1: impl Trait (azúcar sintáctico, dispatch estático)
fn imprimir_area(forma: &impl Area) {
println!("Área: {:.4}", forma.area());
}
// Sintaxis 2: trait bound explícito (más flexible con genéricos)
fn imprimir_area_generica<T: Area>(forma: &T) {
println!("Área: {:.4}", forma.area());
}
// Con where clause (más legible cuando hay múltiples bounds)
fn comparar_areas<T>(forma1: &T, forma2: &T) -> bool
where
T: Area,
{
forma1.area() > forma2.area()
}
fn main() {
let c = Cuadrado { lado: 4.0 };
let r = Circulo { radio: 3.0 };
imprimir_area(&c);
imprimir_area_generica(&r);
println!("¿Cuadrado tiene más área? {}", comparar_areas(&c, &r));
}Múltiples trait bounds
use std::fmt::{Debug, Display};
fn imprimir_debug_y_display<T>(valor: &T)
where
T: Debug + Display,
{
println!("Debug: {:?}", valor);
println!("Display: {}", valor);
}
fn comparar_y_mostrar<T>(a: T, b: T) -> T
where
T: PartialOrd + Display + Clone,
{
if a > b {
println!("{a} > {b}");
a
} else {
println!("{a} <= {b}");
b.clone()
}
}
fn main() {
imprimir_debug_y_display(&42);
imprimir_debug_y_display(&"hola");
let mayor = comparar_y_mostrar(10, 20);
println!("Mayor: {mayor}");
}Supertraits
Un trait puede requerir que el tipo implemente otro trait (supertrait):
use std::fmt;
// Animal requiere que el tipo implemente fmt::Display
trait Animal: fmt::Display {
fn sonido(&self) -> &str;
fn patas(&self) -> u8;
fn describir(&self) -> String {
format!("{} hace '{}' y tiene {} patas", self, self.sonido(), self.patas())
}
}
struct Perro { nombre: String }
struct Gato { nombre: String }
impl fmt::Display for Perro {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "Perro({})", self.nombre)
}
}
impl fmt::Display for Gato {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "Gato({})", self.nombre)
}
}
impl Animal for Perro {
fn sonido(&self) -> &str { "guau" }
fn patas(&self) -> u8 { 4 }
}
impl Animal for Gato {
fn sonido(&self) -> &str { "miau" }
fn patas(&self) -> u8 { 4 }
}
fn main() {
let perro = Perro { nombre: String::from("Rex") };
let gato = Gato { nombre: String::from("Whiskers") };
println!("{}", perro.describir());
println!("{}", gato.describir());
}Implementar Display y Debug
Los traits Display y Debug son fundamentales para hacer que tus tipos sean imprimibles:
use std::fmt;
struct Temperatura {
celsius: f64,
}
// Display: para output orientado al usuario
impl fmt::Display for Temperatura {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{:.1}°C", self.celsius)
}
}
// Debug: para output de depuración
impl fmt::Debug for Temperatura {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("Temperatura")
.field("celsius", &self.celsius)
.field("fahrenheit", &(self.celsius * 1.8 + 32.0))
.finish()
}
}
fn main() {
let t = Temperatura { celsius: 100.0 };
println!("{t}"); // 100.0°C (Display)
println!("{t:?}"); // Debug format
println!("{t:#?}"); // Debug pretty-print
}Trait objects con dyn
Cuando necesitas una colección de tipos distintos que comparten un trait, usa trait objects:
use std::fmt;
trait Dibujable {
fn dibujar(&self);
}
struct Circulo { radio: f64 }
struct Cuadrado { lado: f64 }
struct Triangulo { base: f64, altura: f64 }
impl Dibujable for Circulo {
fn dibujar(&self) { println!("Dibujando círculo (r={})", self.radio); }
}
impl Dibujable for Cuadrado {
fn dibujar(&self) { println!("Dibujando cuadrado (l={})", self.lado); }
}
impl Dibujable for Triangulo {
fn dibujar(&self) { println!("Dibujando triángulo (b={}, h={})", self.base, self.altura); }
}
fn dibujar_todo(formas: &[Box<dyn Dibujable>]) {
for forma in formas {
forma.dibujar();
}
}
fn main() {
// Vec de tipos distintos que implementan Dibujable
let lienzo: Vec<Box<dyn Dibujable>> = vec![
Box::new(Circulo { radio: 5.0 }),
Box::new(Cuadrado { lado: 3.0 }),
Box::new(Triangulo { base: 4.0, altura: 6.0 }),
Box::new(Circulo { radio: 2.5 }),
];
dibujar_todo(&lienzo);
}Los traits más importantes de std
| Trait | Propósito |
|---|---|
Clone |
Copia explícita del valor |
Copy |
Copia implícita (solo stack) |
Debug |
Formateo para depuración {:?} |
Display |
Formateo para usuarios {} |
PartialEq / Eq |
Comparación de igualdad == |
PartialOrd / Ord |
Comparación de orden <, > |
Hash |
Para usar como clave en HashMap |
Iterator |
Protocolo de iteración |
From / Into |
Conversiones entre tipos |
Default |
Valor por defecto |
Send / Sync |
Seguridad en concurrencia |
Los traits son la base de todas las abstracciones en Rust. La próxima lección explora los generics — cómo escribir código que funciona con múltiples tipos sin duplicación.
use std::fmt;
// Definir un trait
trait Describible {
fn descripcion(&self) -> String;
// Método con implementación por defecto
fn descripcion_corta(&self) -> String {
let d = self.descripcion();
if d.len() > 50 {
format!("{}...", &d[..47])
} else {
d
}
}
}
struct Libro {
titulo: String,
autor: String,
paginas: u32,
}
struct Pelicula {
titulo: String,
director: String,
duracion_min: u32,
}
impl Describible for Libro {
fn descripcion(&self) -> String {
format!("'{}' de {} ({} páginas)", self.titulo, self.autor, self.paginas)
}
}
impl Describible for Pelicula {
fn descripcion(&self) -> String {
format!("'{}' dirigida por {} ({} min)", self.titulo, self.director, self.duracion_min)
}
}
fn imprimir_descripcion(item: &impl Describible) {
println!("{}", item.descripcion());
}
fn main() {
let libro = Libro {
titulo: String::from("El Señor de los Anillos"),
autor: String::from("J.R.R. Tolkien"),
paginas: 1178,
};
let pelicula = Pelicula {
titulo: String::from("Inception"),
director: String::from("Christopher Nolan"),
duracion_min: 148,
};
imprimir_descripcion(&libro);
imprimir_descripcion(&pelicula);
println!("Corto: {}", libro.descripcion_corta());
}
Inicia sesión para guardar tu progreso