Despacho dinámico
Parte de la sección Programación Orientada a Objetos del Journey de Rust de Coddy. Lección 45 de 61.
Cuando usas genéricos con restricciones de traits como <T: Summary>, Rust determina el tipo exacto en tiempo de compilación. Esto se denomina despacho estático: el compilador genera código especializado para cada tipo concreto que usas. Es rápido, pero existe una limitación: una variable solo puede contener un tipo específico.
¿Qué pasa si necesitas una sola variable que pueda contener tipos diferentes en tiempo de ejecución? Aquí es donde entran en juego los objetos de rasgo. 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 el despacho dinámico: Rust busca qué method llamar en tiempo de ejecución en lugar de hacerlo en tiempo de compilación. Box es necesario porque los objetos de rasgo no tienen un tamaño conocido; Box proporciona un puntero con un tamaño fijo.
Piénsalo así: los genéricos dicen «trabajo con el tipo T», mientras que los objetos de rasgo dicen «trabajo con cualquier cosa que pueda hacer esto». La compensación es un pequeño coste en tiempo de ejecución a cambio de la flexibilidad de manejar distintos tipos mediante la misma variable.
Desafío
Fácil¡Construyamos un sistema de alquiler de vehículos que demuestre el poder del despacho dinámico! Crearás un trait que define lo que cualquier vehículo alquilable puede hacer y, después, usarás Box<dyn Trait> para almacenar distintos 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 recibe&selfy devuelve unString. Después, crea dos structs públicos:Car: con un campo públicomodel(String). Su métodorental_infodebe devolverCar: {model}Bike: con un campo públicobrand(String). Su métodorental_infodebe devolverBike: {brand}
main.rs: Incluye tu módulo de vehículos 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, después reasigna la misma variable para que contenga unBikee imprime de nuevo su información de alquiler.
La magia aquí es que una sola variable de tipo Box<dyn Rentable> puede contener un Car o un Bike: Rust determina qué método rental_info debe llamar en tiempo de ejecución. ¡Este 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 automóvil y la marca de la bicicleta.
Pruébalo tú mismo
mod vehicle;
use vehicle::{Rentable, Car, Bike};
fn main() {
// Leer la 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: Crea una variable Box<dyn Rentable> que contenga un Car
// Imprime su información de alquiler usando el método rental_info()
// TODO: Reasigna la misma variable para que contenga un Bike
// Imprime 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 Programación Orientada a Objetos
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ógicoPractica por tu cuenta: Compilador de Rust online