Traits en Rust: define comportamiento compartido entre tipos distintos

Los traits definen un conjunto de métodos que un tipo puede implementar. Son el mecanismo de Rust para el polimorfismo: en lugar de herencia, compartes comportamiento implementando traits. Si conoces las interfaces de Java o TypeScript, los traits son conceptualmente similares, aunque con diferencias importantes.

Definir un trait

trait Resumen {
    fn resumir(&self) -> String;
}

Solo defines la firma. Cada tipo que implemente Resumen debe proporcionar su propia versión de resumir.

Implementar un trait

struct Articulo {
    titulo: String,
    autor: String,
    contenido: String,
}

impl Resumen for Articulo {
    fn resumir(&self) -> String {
        format!("{}, por {}", self.titulo, self.autor)
    }
}

struct Tweet {
    usuario: String,
    texto: String,
}

impl Resumen for Tweet {
    fn resumir(&self) -> String {
        format!("{}: {}", self.usuario, self.texto)
    }
}

fn main() {
    let art = Articulo {
        titulo: String::from("Rust 2026"),
        autor: String::from("Ana"),
        contenido: String::from("..."),
    };

    let tweet = Tweet {
        usuario: String::from("@ana"),
        texto: String::from("Aprendiendo Rust"),
    };

    println!("{}", art.resumir());
    println!("{}", tweet.resumir());
}

Implementaciones por defecto

Los traits pueden tener implementaciones por defecto que los tipos pueden usar o sobreescribir:

trait Resumen {
    fn autor(&self) -> String;

    // Implementación por defecto que usa autor()
    fn resumir(&self) -> String {
        format!("(Leer más de {}...)", self.autor())
    }
}

struct Tweet {
    usuario: String,
    texto: String,
}

impl Resumen for Tweet {
    fn autor(&self) -> String {
        format!("@{}", self.usuario)
    }
    // resumir() usa la implementación por defecto
}

fn main() {
    let t = Tweet { usuario: String::from("ana"), texto: String::from("hola") };
    println!("{}", t.resumir()); // (Leer más de @ana...)
}

Traits como parámetros: impl Trait

fn notificar(item: &impl Resumen) {
    println!("Notificación: {}", item.resumir());
}

fn main() {
    let art = Articulo { /* ... */ };
    notificar(&art);
}

impl Trait en la posición de parámetro significa "cualquier tipo que implemente este trait". El compilador genera código especializado para cada tipo (monomorfización).

El orphan rule

Solo puedes implementar un trait para un tipo si el trait o el tipo están definidos en tu crate. No puedes implementar Display de la stdlib para Vec de la stdlib: ambos son externos.

// OK: el trait (Resumen) es tuyo
impl Resumen for Vec<String> { /* ... */ }

// OK: el tipo (MiStruct) es tuyo
impl std::fmt::Display for MiStruct { /* ... */ }

// ERROR: ambos son de la stdlib
// impl std::fmt::Display for Vec<String> { /* ... */ }

Esta regla evita conflictos entre crates que implementaran el mismo trait para el mismo tipo.

Traits con generics

trait Convertir<T> {
    fn convertir(&self) -> T;
}

struct Celsius(f64);
struct Fahrenheit(f64);

impl Convertir<Fahrenheit> for Celsius {
    fn convertir(&self) -> Fahrenheit {
        Fahrenheit(self.0 * 9.0 / 5.0 + 32.0)
    }
}

fn main() {
    let c = Celsius(100.0);
    let f: Fahrenheit = c.convertir();
    println!("{}°F", f.0); // 212°F
}

Resumen

  • Un trait define un conjunto de métodos que un tipo debe implementar.
  • Puedes tener implementaciones por defecto para métodos no críticos.
  • impl Trait en parámetros acepta cualquier tipo que implemente ese trait.
  • El orphan rule impide implementar traits externos para tipos externos.
  • Los traits son el mecanismo central de polimorfismo en Rust, sin herencia.

El siguiente artículo cubre los trait bounds: cómo restringir los tipos que acepta una función genérica, la cláusula where, impl Trait frente a T: Trait y las blanket implementations.

COMPARTE ESTE ARTÍCULO

COMPARTIR EN FACEBOOK
COMPARTIR EN TWITTER
COMPARTIR EN LINKEDIN
COMPARTIR EN WHATSAPP