Translate

miércoles, 12 de agosto de 2020

Trait Objects para hacer Dynamic Dispatch en Rust


Seguimos con rust y el polimorfismo. 

Como sabemos el Dynamic Dispatch significa que el compilador llama código que es determinado en tiempo de ejecución.

Cuando usamos objetos de trait, Rust debe usar Dynamic Dispatch. El compilador no conoce todos los tipos que podrían usarse con el código que usa objetos de trait, por lo que no sabe qué método implementado en qué tipo llamar. En cambio, en tiempo de ejecución, Rust usa los punteros dentro del objeto de trait para saber a qué método llamar. Hay un costo de tiempo de ejecución cuando ocurre esta búsqueda que no ocurre con el envío estático. El envío dinámico también evita que el compilador elija insertar el código de un método, lo que a su vez evita algunas optimizaciones. Sin embargo, obtuvimos una flexibilidad adicional propia del polimorfismo.

Algunas reglas complejas gobiernan todas las propiedades que hacen que un objeto de trait sea seguro, pero solo dos reglas son relevantes. Un trait es seguro para objetos si todos los métodos definidos en el trait tienen las siguientes propiedades:

  • El tipo de retorno no es Self.
  • No hay parámetros de tipo genérico.

La palabra clave Self es un alias para el tipo en el que estamos implementando los métodos. Los objetos de trait deben ser seguros para objetos porque una vez que has usado un objeto de rasgo, Rust ya no sabe el tipo concreto que está implementando ese rasgo. Si un método de rasgo devuelve el tipo Self concreto, pero un objeto rasgo olvida el tipo exacto que es Self, no hay forma de que el método pueda usar el tipo concreto original. Lo mismo ocurre con los parámetros de tipo genérico que se completan con parámetros de tipo concreto cuando se utiliza el rasgo: los tipos concretos pasan a formar parte del tipo que implementa el rasgo. Cuando el tipo se olvida mediante el uso de un objeto de rasgo, no hay forma de saber con qué tipos completar los parámetros de tipo genérico.

Un ejemplo de un trait cuyos métodos no son seguros para objetos es el trait Clonar de la biblioteca estándar. La firma para el método de clonación en el rasgo Clonar se ve así:

pub trait Clone {

    fn clone(&self) -> Self;

}

El tipo String implementa el rasgo Clone, y cuando llamamos al método clone en una instancia de String, obtenemos una instancia de String. De manera similar, si llamamos a clonar en una instancia de Vec <T>, obtenemos una instancia de Vec <T>. La firma del clon necesita saber qué tipo sustituirá a Self, porque ese es el tipo de retorno.

El compilador le indicará cuando esté intentando hacer algo que viole las reglas de seguridad de los objetos con respecto a los objetos característicos. Por ejemplo, digamos que intentamos implementar la estructura Screen en el Listado 17-4 para contener los tipos que implementan el rasgo Clonar en lugar del rasgo Dibujar, así:

pub struct Screen {

    pub components: Vec<Box<dyn Clone>>,

}

Obtendríamos este error:

$ cargo build Compiling gui v0.1.0 (file:///projects/gui) error[E0038]: the trait `std::clone::Clone` cannot be made into an object --> src/lib.rs:2:5 | 2 | pub components: Vec<Box<dyn Clone>>, | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ the trait `std::clone::Clone` cannot be made into an object | = note: the trait cannot require that `Self : Sized` error: aborting due to previous error For more information about this error, try `rustc --explain E0038`. error: could not compile `gui`. To learn more, run the command again with --verbose.

Este error significa que no puede usar este trait de esta manera. Si está interesado en obtener más detalles sobre la seguridad de los objetos, consulte Rust RFC 255.

Dejo link: https://doc.rust-lang.org/book/ch17-02-trait-objects.html