# Traits y genéricos en Rust: contratos explícitos, despacho estático y dinámico

En [Ownership en Rust](/post/ownership-en-rust) y [Borrowing en Rust](/post/borrowing-en-rust) conté cómo decide Rust quién es dueño de cada valor y quién puede tocarlo mientras tanto. Por el camino aparecieron, sin presentarse, unas cuantas palabras raras: `Copy`, `Clone`, `Drop`, `Send`. Todas son **traits**, y hoy toca explicar qué son.

Durante años usé Go para todo lo que no hacía con Rails, y la pieza equivalente allí es la interfaz: un conjunto de métodos que un tipo cumple **sin declararlo**. En Rust el contrato se firma con todas las letras, y esa diferencia arrastra todo lo demás: los genéricos, cuándo se paga una llamada indirecta y qué puedes implementar y dónde. Todo está compilado con Rust 1.98 y la edición 2024, y los errores son la salida real de `cargo`, recortada donde sobra.

## Un trait es un contrato que se firma

Un trait declara métodos que un tipo se compromete a tener. Puede traer además **métodos por defecto**, con cuerpo, que el tipo hereda si no los reescribe:

```rust
trait Resumen {
    fn titulo(&self) -> &str;

    // Método por defecto: quien implemente el trait lo hereda gratis.
    fn resumen(&self) -> String {
        format!("{} (leer más)", self.titulo())
    }
}

struct Post {
    titulo: String,
    palabras: usize,
}

struct Pagina {
    titulo: String,
}

impl Resumen for Post {
    fn titulo(&self) -> &str {
        &self.titulo
    }

    fn resumen(&self) -> String {
        format!("{}, {} min de lectura", self.titulo, self.palabras / 200)
    }
}

impl Resumen for Pagina {
    fn titulo(&self) -> &str {
        &self.titulo
    }
}
```

Un `Post` de 2.400 palabras imprime «Traits y genéricos en Rust, 12 min de lectura»; una `Pagina` se conforma con el resumen por defecto, «Sobre mí (leer más)». En Go la misma idea se escribe sin `impl`:

```go
type Resumen interface {
	Resumen() string
}

type Post struct{ Titulo string }

// Ningún "implements": basta con tener el método.
func (p Post) Resumen() string { return "post: " + p.Titulo }

var _ Resumen = Post{} // comprobación opcional en compilación
```

La satisfacción implícita de Go tiene una ventaja real: tu tipo cumple interfaces definidas después, en otro paquete, sin enterarse. La de Rust tiene otras. La intención queda escrita: un método que se llama igual por casualidad no convierte a nadie en `Resumen`. Hay métodos por defecto, que en Go no existen y se suplen con funciones sueltas o con *embedding*. Y, como el `impl` es un bloque aparte del tipo, **puedes implementar tu trait para tipos que no son tuyos**, incluso `String` o `Vec<T>`. Go no te deja añadir métodos a un tipo de otro paquete: `cannot define new methods on non-local type time.Duration`.

## Genéricos con trait bounds

Una función genérica en Rust no puede hacer nada con un `T` que el trait no prometa. Ni siquiera imprimirlo:

```rust
fn etiqueta<T>(valor: T) -> String {
    format!("[{valor}]")
}
```

```text
error[E0277]: `T` doesn't implement `std::fmt::Display`
 --> src/main.rs:2:15
  |
2 |     format!("[{valor}]")
  |               ^^^^^^^ `T` cannot be formatted with the default formatter
  |
help: consider restricting type parameter `T` with trait `Display`
  |
1 | fn etiqueta<T: std::fmt::Display>(valor: T) -> String {
  |              +++++++++++++++++++
```

E0277, *trait bound not satisfied*, es el error que más verás con genéricos, y aquí es instructivo dónde salta: en la definición, aunque nadie haya llamado a la función. A diferencia de las plantillas de C++, el cuerpo de una función genérica se comprueba contra sus restricciones, no contra los tipos con los que se use. Si compila, compila para cualquier `T` que las cumpla, y los errores de quien la use saltan en la llamada.

La restricción, el *trait bound*, admite tres sintaxis:

```rust
use std::fmt::{Debug, Display};

fn etiqueta<T: Display>(valor: T) -> String {
    format!("[{valor}]")
}

fn depurar<T, U>(clave: T, valor: U) -> String
where
    T: Display,
    U: Debug + Clone,
{
    format!("{clave} = {valor:?}")
}

fn etiqueta_corta(valor: impl Display) -> String {
    format!("[{valor}]")
}

struct Post {
    slug: &'static str,
    borrador: bool,
}

fn publicados(posts: &[Post]) -> impl Iterator<Item = &str> {
    posts.iter().filter(|p| !p.borrador).map(|p| p.slug)
}
```

`T: Display` va en línea; `where` es lo mismo, más legible cuando hay varios parámetros o varios bounds unidos con `+`; e `impl Display` en un argumento es azúcar para un genérico anónimo. En el retorno, `impl Trait` significa otra cosa: la función devuelve **un único tipo concreto** que el llamador no puede nombrar, solo usar como iterador. Es la forma normal de devolver una cadena de `filter` y `map`, cuyo tipo real nadie quiere escribir.

En Go, desde la 1.18, [la restricción es una interfaz](/post/go-generics-en-la-practica) entre corchetes: `func Etiqueta[T fmt.Stringer](v T) string`. La idea es la misma. Lo que cambia es lo que ocurre al compilar.

## Despacho estático o dinámico

Rust **monomorfiza**: por cada tipo concreto con el que llamas a una función genérica genera una copia especializada, como si la hubieras escrito a mano para ese tipo. El libro lo llama *static dispatch*, porque el compilador sabe en tiempo de compilación qué método se llama y puede integrarlo en línea.

Cuando necesitas mezclar tipos distintos en la misma colección, la monomorfización no basta: un `Vec<T>` tiene un solo `T`. Para eso existen los **trait objects**, `dyn Trait`:

```rust
// Estático: el compilador genera una copia por cada tipo concreto.
fn imprimir(item: &impl Resumen) {
    println!("{}", item.resumen());
}

fn main() {
    imprimir(&Post { titulo: "Traits en Rust".into() });

    // Dinámico: un mismo Vec con tipos distintos detrás de un puntero.
    let portada: Vec<Box<dyn Resumen>> = vec![
        Box::new(Post { titulo: "Ownership en Rust".into() }),
        Box::new(Pagina { titulo: "Sobre mí".into() }),
    ];
    for item in &portada {
        println!("{}", item.resumen()); // se resuelve en ejecución, vía vtable
    }

    println!("&Post: {} bytes", size_of::<&Post>());
    println!("&dyn Resumen: {} bytes", size_of::<&dyn Resumen>());
}
```

```text
post: Traits en Rust
post: Ownership en Rust
página: Sobre mí
&Post: 8 bytes
&dyn Resumen: 16 bytes
```

Una referencia a un trait object es un **puntero ancho**: un puntero al dato y otro a la *vtable*, la tabla con las direcciones de los métodos de ese tipo. Cada llamada busca el método en la tabla en tiempo de ejecución, y el libro avisa de que eso impide al compilador integrar el código en línea y las optimizaciones que vienen detrás. `Box<dyn Resumen>` es lo mismo, pero dueño del valor en el montículo.

Esos 16 bytes le sonarán a quien venga de Go: un valor de interfaz también son dos palabras, un puntero al *itab*, con el tipo y sus métodos, y otro al dato, y `unsafe.Sizeof` de una interfaz da 16 en 64 bits. La diferencia es de valor por defecto. En Go, **llamar a un método a través de una interfaz es despacho dinámico**, salvo que el compilador logre desvirtualizar la llamada: lo hace en algunos casos en los que ve el tipo concreto y, desde Go 1.21, con PGO, que añade una llamada directa al tipo más habitual. Y sus genéricos no monomorfizan del todo: generan una implementación por *GC shape* y le pasan en cada llamada un diccionario con la información de tipos; todos los punteros comparten forma. En Rust el camino por defecto es el estático, y `dyn` es una decisión explícita que se ve en la firma.

La monomorfización tampoco es gratis. Cada combinación de tipos es código nuevo que LLVM tiene que optimizar, y el resultado ocupa sitio en el binario. Una función genérica grande usada con veinte tipos son, en principio, veinte funciones. Cuando eso pesa, `dyn` cambia algo de velocidad en ejecución por menos código. Lo razonable es empezar por genéricos y pasar a `dyn` cuando necesites colecciones heterogéneas, *plugins* o frenar el crecimiento del binario.

### No todo trait puede ser dyn

Para construir la vtable, cada método tiene que tener una única dirección. Un método genérico tiene una por tipo, así que no cabe:

```rust
use std::io::{self, Write};

trait Exportable {
    fn exportar<W: Write>(&self, salida: W) -> io::Result<()>;
}
```

```text
error[E0038]: the trait `Exportable` is not dyn compatible
  --> src/main.rs:16:27
   |
16 |     let cola: Vec<Box<dyn Exportable>> = vec![Box::new(Post)];
   |                           ^^^^^^^^^^ `Exportable` is not dyn compatible
   |
note: for a trait to be dyn compatible it needs to allow building a vtable
  --> src/main.rs:4:8
   |
 3 | trait Exportable {
   |       ---------- this trait is not dyn compatible...
 4 |     fn exportar<W: Write>(&self, salida: W) -> io::Result<()>;
   |        ^^^^^^^^ ...because method `exportar` has generic type parameters
```

Si has leído sobre Rust hace unos años, esto se llamaba *object safety*. La referencia del lenguaje y el compilador lo llaman ahora **dyn compatibility**, y la referencia mantiene una nota que recuerda el nombre antiguo. Las reglas impiden, entre otras cosas, métodos con parámetros de tipo, métodos que usen `Self` fuera del receptor, constantes asociadas o `Self: Sized` como supertrait. Hay dos salidas: cambiar el genérico por un trait object, `fn exportar(&self, salida: &mut dyn Write)`, o añadir `where Self: Sized` al método, que lo saca de la vtable y deja el resto del trait usable como `dyn`.

## Traits de la biblioteca estándar que conviene conocer

De `Copy` y `Clone` ya hablé en el post de [ownership](/post/ownership-en-rust). Estos son los siguientes de la lista, casi todos en un ejemplo:

```rust
use std::fmt;

#[derive(Debug, Clone, PartialEq, Default)]
struct Slug(String);

impl From<&str> for Slug {
    fn from(titulo: &str) -> Self {
        let palabras: Vec<String> = titulo.split_whitespace().map(str::to_lowercase).collect();
        Slug(palabras.join("-"))
    }
}

impl fmt::Display for Slug {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        write!(f, "/post/{}", self.0)
    }
}

fn main() {
    let a = Slug::from("Traits y genéricos en Rust");
    let b: Slug = "traits y GENÉRICOS en rust".into(); // Into sale gratis de From
    println!("{a}"); // Display, escrito a mano
    println!("{b:?}"); // Debug, derivado
    println!("{}", a == b); // PartialEq, derivado
    println!("{:?}", Slug::default()); // Default, derivado
}
```

```text
/post/traits-y-genéricos-en-rust
Slug("traits-y-genéricos-en-rust")
true
Slug("")
```

- **`Display` y `Debug`**: `{}` es para personas y se escribe a mano; `{:?}` es para programadores y casi siempre se deriva.
- **`#[derive]`** genera la implementación de `Debug`, `Clone`, `PartialEq`, `Default` y compañía campo a campo, siempre que todos los campos los implementen.
- **`From` e `Into`**: implementas `From<&str> for Slug` y obtienes `Into<Slug> for &str` gratis, porque la biblioteca estándar tiene un *blanket impl*, una implementación para todo tipo que cumpla una condición. Por eso la documentación pide implementar siempre `From`. Lo mismo pasa con `ToString`: cualquier tipo con `Display` tiene `.to_string()`.

El otro trait que hay que conocer es **`Iterator`**. Basta con implementar `next` para recibir gratis los métodos por defecto: `map`, `filter`, `skip`, `sum`, `collect` y decenas más. Las páginas de la portada de este blog, de cinco en cinco:

```rust
struct Paginas {
    actual: usize,
    total: usize,
}

impl Iterator for Paginas {
    type Item = usize;

    fn next(&mut self) -> Option<Self::Item> {
        if self.actual == self.total {
            return None;
        }
        self.actual += 1;
        Some(self.actual)
    }
}

fn main() {
    let total = 23_usize.div_ceil(5); // 23 posts, 5 por página
    let urls: Vec<String> = Paginas { actual: 0, total }
        .skip(1)
        .map(|n| format!("/?page={n}"))
        .collect();
    println!("{urls:?}"); // ["/?page=2", "/?page=3", "/?page=4", "/?page=5"]
}
```

`next` devuelve un `Option`, que es `Some(valor)` mientras queda algo y `None` al final; le dedico el próximo post.

## Tipos asociados frente a parámetros genéricos

Fíjate en `type Item = usize`. `Item` es un **tipo asociado**: lo elige cada implementación, una sola vez. Podría haberse diseñado como `Iterator<T>`, y el libro explica por qué no: con un parámetro genérico, un mismo tipo podría implementar `Iterator<usize>` e `Iterator<String>` a la vez, y cada llamada a `next` necesitaría una anotación para saber cuál usar. Con el tipo asociado solo puede haber un `impl Iterator for Paginas`, y el compilador sabe qué sale de él.

`From<T>`, en cambio, es genérico a propósito: `String` implementa `From<&str>`, `From<char>`, `From<Box<str>>` y varios más, porque tiene sentido construir un `String` desde muchos orígenes. La regla práctica: **parámetro genérico si un tipo puede implementar el trait varias veces con sentido; tipo asociado si para cada tipo hay una única respuesta natural.**

## Coherencia, huérfanos y newtype

Para que `a == b` o `x.to_string()` signifiquen siempre lo mismo, Rust exige **coherencia**: para cada par de trait y tipo hay, como mucho, una implementación en todo el programa. La herramienta que la garantiza entre crates es la **regla del huérfano** (*orphan rule*): solo puedes implementar un trait si el trait o el tipo son de tu crate. Implementar uno ajeno para un tipo ajeno no se puede:

```rust
use std::fmt;

impl fmt::Display for Vec<String> {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        write!(f, "#{}", self.join(" #"))
    }
}
```

```text
error[E0117]: only traits defined in the current crate can be implemented for types defined outside of the crate
 --> src/main.rs:3:1
  |
3 | impl fmt::Display for Vec<String> {
  | ^^^^^^^^^^^^^^^^^^^^^^-----------
  |                       |
  |                       `Vec` is not defined in the current crate
  |
  = note: define and implement a trait or new type instead
```

La razón, según la referencia, es que si se permitiera, dos crates podrían implementar el mismo trait para el mismo tipo de formas incompatibles, y **actualizar una dependencia podría romper la compilación** de un programa que no ha cambiado.

La nota del final propone la salida estándar, el patrón **newtype**: un struct tupla con un único campo, que es tuyo y por tanto admite el `impl`:

```rust
struct Tags(Vec<String>);

impl fmt::Display for Tags {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        write!(f, "#{}", self.0.join(" #"))
    }
}
```

Con `rust`, `go` y `traits` dentro imprime `#rust #go #traits`. El envoltorio no cuesta nada en ejecución, porque el compilador lo elimina; el precio es que `Tags` no tiene los métodos de `Vec`, y hay que delegarlos o implementar `Deref`. El `Slug(String)` de antes era otro newtype, en ese caso para que un slug y un título no se confundan aunque ambos sean texto.

Lo curioso es que Go llega al mismo sitio por otro camino. No permite métodos en tipos de otro paquete, así que para dar un `String()` a un `[]string` también defines `type Tags []string`. La diferencia es que en Go el problema solo afecta a los métodos; en Rust afecta a los traits, que son los que deciden si `{}` o `==` funcionan con tu tipo.

## Lo que viene

Resumiendo: un trait es un contrato explícito; los genéricos se restringen con bounds que se comprueban en la definición; Rust monomorfiza por defecto y solo despacha en ejecución cuando escribes `dyn`; y la regla del huérfano mantiene la coherencia, con el newtype como salida. Frente a Go, ganas control sobre cuándo pagas una indirección y pierdes la comodidad de cumplir interfaces sin decirlo.

En los ejemplos de hoy se ha colado dos veces un tipo que no he explicado: el `Option` que devuelve `next` y el `io::Result` de `exportar`. Son enums, y junto con el pattern matching y el operador `?` forman la forma en que Rust maneja la ausencia y los errores. De eso va [Option y Result en Rust](/post/option-y-result-en-rust), el lunes.
