Menu
Coddy logo textTech

Despacho dinámico

Parte de la sección Object Oriented Programming del Journey de Rust de Coddy — lección 45 de 61.

Cuando usas genéricos con límites de rasgo (trait bounds) como <T: Summary>, Rust determina el tipo exacto en tiempo de compilación. Esto se llama despacho estático—el compilador genera código especializado para cada tipo concreto que utilices. Es rápido, pero hay una limitación: una variable solo puede contener un tipo específico.

¿Qué pasa si necesitas una sola variable que pueda contener diferentes tipos en tiempo de ejecución? Aquí es donde entran en juego los objetos de rasgo (trait objects). Usando Box<dyn Trait>, puedes almacenar cualquier tipo que implemente el rasgo:

trait Speak {
    fn speak(&self) -> String;
}

struct Dog;
struct Cat;

impl Speak for Dog {
    fn speak(&self) -> String { String::from("Woof!") }
}

impl Speak for Cat {
    fn speak(&self) -> String { String::from("Meow!") }
}

fn main() {
    let animal: Box<dyn Speak> = Box::new(Dog);
    println!("{}", animal.speak());  // "Woof!"
    
    let animal: Box<dyn Speak> = Box::new(Cat);
    println!("{}", animal.speak());  // "Meow!"
}

La palabra clave dyn indica despacho dinámico: Rust busca qué método llamar en tiempo de ejecución en lugar de en tiempo de compilación. El Box es necesario porque los objetos de rasgo (trait objects) no tienen un tamaño conocido; Box proporciona un puntero con un tamaño fijo.

Piénsalo de esta manera: los genéricos dicen "Trabajo con el tipo T", mientras que los objetos de rasgo (trait objects) dicen "Trabajo con cualquier cosa que pueda hacer esto". La contrapartida es un pequeño coste en tiempo de ejecución a cambio de la flexibilidad de manejar diferentes tipos a través de la misma variable.

challenge icon

Desafío

Fácil

¡Vamos a construir un sistema de alquiler de vehículos que demuestre el poder del despacho dinámico (dynamic dispatch)! Crearás un trait que define lo que cualquier vehículo alquilable puede hacer, luego usarás Box<dyn Trait> para almacenar diferentes tipos de vehículos en la misma variable.

Organizarás tu código en dos archivos:

  • vehicle.rs: Define un trait público Rentable con un método rental_info que tome &self y devuelva un String. Luego crea dos structs públicos:
    • Car — con un campo público model (String). Su rental_info debe devolver "Car: {model}"
    • Bike — con un campo público brand (String). Su rental_info debe devolver "Bike: {brand}"
  • main.rs: Importa tu módulo vehicle y usa las entradas para demostrar el despacho dinámico. Crea una variable Box<dyn Rentable> que primero contenga un Car, imprime su información de alquiler, luego reasigna la misma variable para que contenga una Bike e imprime su información de alquiler nuevamente.

La magia aquí es que una sola variable de tipo Box<dyn Rentable> puede contener tanto un Car como una Bike; Rust determina qué método rental_info llamar en tiempo de ejecución. ¡Esto es el despacho dinámico en acción!

Tu salida debe mostrar la información de ambos vehículos:

Car: {model}
Bike: {brand}

Por ejemplo, con las entradas "Tesla Model 3" y "Trek":

Car: Tesla Model 3
Bike: Trek

Recibirás dos entradas: el modelo del coche y la marca de la bicicleta.

Hoja de referencia

Rust utiliza el despacho estático (static dispatch) con genéricos: el compilador genera código especializado para cada tipo en tiempo de compilación. Esto es rápido, pero significa que una variable solo puede contener un tipo específico.

Los objetos de rasgo (trait objects) permiten el despacho dinámico (dynamic dispatch), lo que permite que una sola variable contenga diferentes tipos en tiempo de ejecución. Usa Box<dyn Trait> para almacenar cualquier tipo que implemente el rasgo:

trait Speak {
    fn speak(&self) -> String;
}

struct Dog;
struct Cat;

impl Speak for Dog {
    fn speak(&self) -> String { String::from("Woof!") }
}

impl Speak for Cat {
    fn speak(&self) -> String { String::from("Meow!") }
}

fn main() {
    let animal: Box<dyn Speak> = Box::new(Dog);
    println!("{}", animal.speak());  // "Woof!"
    
    let animal: Box<dyn Speak> = Box::new(Cat);
    println!("{}", animal.speak());  // "Meow!"
}

La palabra clave dyn indica despacho dinámico: las llamadas a los métodos se resuelven en tiempo de ejecución. Box es necesario porque los objetos de rasgo no tienen un tamaño conocido en tiempo de compilación.

Diferencia clave: Los genéricos dicen "trabajo con el tipo T" (un tipo específico), mientras que los objetos de rasgo dicen "trabajo con cualquier cosa que implemente este rasgo" (múltiples tipos a través de la misma variable). La contrapartida es un pequeño coste en tiempo de ejecución a cambio de flexibilidad.

Pruébalo tú mismo

mod vehicle;

use vehicle::{Rentable, Car, Bike};

fn main() {
    // Leer entrada
    let mut car_model = String::new();
    std::io::stdin().read_line(&mut car_model).expect("Failed to read line");
    let car_model = car_model.trim().to_string();
    
    let mut bike_brand = String::new();
    std::io::stdin().read_line(&mut bike_brand).expect("Failed to read line");
    let bike_brand = bike_brand.trim().to_string();
    
    // TODO: Crear una variable Box<dyn Rentable> que contenga un Car
    // Imprimir su información de alquiler usando el método rental_info()
    
    // TODO: Reasignar la misma variable para que contenga una Bike
    // Imprimir su información de alquiler de nuevo
    
}
quiz iconPonte a prueba

Esta lección incluye un breve cuestionario. Empieza la lección para responderlo y registrar tu progreso.

Todas las lecciones de Object Oriented Programming