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.
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úblicoRentablecon un métodorental_infoque tome&selfy devuelva unString. Luego crea dos structs públicos:Car— con un campo públicomodel(String). Surental_infodebe devolver"Car: {model}"Bike— con un campo públicobrand(String). Surental_infodebe devolver"Bike: {brand}"
main.rs: Importa tu módulo vehicle y usa las entradas para demostrar el despacho dinámico. Crea una variableBox<dyn Rentable>que primero contenga unCar, imprime su información de alquiler, luego reasigna la misma variable para que contenga unaBikee 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: TrekRecibirá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
}
Esta lección incluye un breve cuestionario. Empieza la lección para responderlo y registrar tu progreso.
Todas las lecciones de Object Oriented Programming
1Métodos y Comportamiento
Intro a Bloques de ImplementaciónEl Parámetro SelfMétodos MutablesFunciones AsociadasMúltiples Bloques de ImplementaciónEncadenamiento de MétodosResumen - Acciones de Rectángulo4Proyecto: Mascota virtual
Definir la mascotaAlimentar a la mascota7Traits estándar
El trait DebugEl trait DisplayClone y CopyTraits de igualdadResumen - Punto imprimible10Proyecto: Sistema de documentos
El Trait DrawComponente de texto2Encapsulamiento y módulos
Conceptos básicos de módulosLa palabra clave PublicCampos privadosGettersSettersRepaso - Casillero seguro5Genéricos
Estructuras genéricasMétodos genéricosMúltiples tipos genéricosFunciones genéricasResumen - Punto de coordenadas8Traits como límites
Sintaxis de límites de traitsLímites múltiplesLa cláusula whereRetornar tipos con traitsResumen: Impresora genérica11Patrones de diseño en Rust
Patrón NewtypeComposiciónEl Trait DropFrom e IntoResumen - Mock de Smart Pointer3Enums avanzados
Enums con datosMétodos en EnumsCoincidencia de variantes de datosEl Enum Option revisitadoResumen - Enum Shape6Definición de Traits
¿Qué es un Trait?Implementación de TraitsImplementaciones por defectoSobrescribir valores por defectoTraits con parámetrosResumen - Reproductor multimedia9Objetos de Trait
Despacho dinámicoVectores de TraitsIteración de objetos de TraitRepaso - Gestor del zoológico