ventajas una sirve que poo para metodo interfaces instanciar implementación ejemplo desventajas clases clase abstracto abstractas abstracta rust traits

rust - una - ¿Por qué debería implementar métodos en un rasgo en lugar de como parte del rasgo?



que es una clase abstracta en poo (2)

Al intentar comprender mejor el rasgo Any , vi que tiene un bloqueo impl para el rasgo en sí . No entiendo el propósito de esta construcción, o incluso si tiene un nombre específico.

Hice un pequeño experimento con un método de rasgo "normal" y un método definido en el bloque impl :

trait Foo { fn foo_in_trait(&self) { println!("in foo") } } impl dyn Foo { fn foo_in_impl(&self) { println!("in impl") } } impl Foo for u8 {} fn main() { let x = Box::new(42u8) as Box<dyn Foo>; x.foo_in_trait(); x.foo_in_impl(); let y = &42u8 as &dyn Foo; y.foo_in_trait(); y.foo_in_impl(); // May cause an error, see below }

Nota del editor

En las versiones de Rust hasta Rust 1.15.0 inclusive, la línea y.foo_in_impl() provoca el error:

error: borrowed value does not live long enough --> src/main.rs:20:14 | 20 | let y = &42u8 as &Foo; | ^^^^ does not live long enough ... 23 | } | - temporary value only lives until here | = note: borrowed value must be valid for the static lifetime...

Este error ya no está presente en las versiones posteriores, pero los conceptos explicados en las respuestas siguen siendo válidos.

A partir de este experimento limitado, parece que los métodos definidos en el bloque impl son más restrictivos que los métodos definidos en el bloque de trait . Es probable que haya algo extra que hacerlo de esta manera desbloquea, ¡pero todavía no sé qué es! ^ _ ^

Las secciones de The Rust Programming Language sobre traits y objetos de rasgos no mencionan esto. Al buscar la fuente Rust en sí, parece que solo Any y Error usan esta característica en particular. No he visto esto usado en el puñado de cajas donde he mirado el código fuente.


Cuando define un rasgo llamado Foo que se puede convertir en un objeto, Rust también define un tipo de objeto de rasgo llamado dyn Foo . En versiones anteriores de Rust, este tipo solo se llamaba Foo (consulte ¿Qué significa "dyn" en un tipo? ). Para la compatibilidad con estas versiones anteriores, Foo todavía trabaja para nombrar el tipo de objeto de rasgo, aunque la sintaxis dyn debe usar para el nuevo código.

Los objetos de rasgo tienen un parámetro de vida útil que designa el más corto de los parámetros de vida útil del implementador. Para especificar esa duración, escriba el tipo como dyn Foo + ''a .

Cuando escribe impl dyn Foo { (o simplemente impl Foo { utilizando la sintaxis anterior), no está especificando ese parámetro de duración, y su valor predeterminado es ''static . Esta nota del compilador en y.foo_in_impl(); declaración insinúa que:

nota: el valor prestado debe ser válido para la vida estática ...

Todo lo que tenemos que hacer para que esto sea más permisivo es escribir un impl genérico durante toda la vida:

impl<''a> dyn Foo + ''a { fn foo_in_impl(&self) { println!("in impl") } }

Ahora, observe que el argumento self en foo_in_impl es un puntero prestado, que tiene un parámetro propio de por vida. El tipo de self , en su forma completa, se ve como &''b (dyn Foo + ''a) (los paréntesis son obligatorios debido a la precedencia del operador). Un Box<u8> posee su u8 , no toma prestado nada, por lo que puede crear un &(dyn Foo + ''static) partir de él. Por otro lado, &42u8 crea a &''b (dyn Foo + ''a) donde ''a no es ''static , porque 42u8 se coloca en una variable oculta en la pila, y el objeto rasgo toma prestada esta variable. (Sin embargo, eso realmente no tiene sentido; u8 no u8 prestado nada, por lo que su implementación de Foo siempre debe ser compatible con dyn Foo + ''static ... el hecho de que 42u8 se 42u8 prestado de la pila debería afectar ''b , no ''a .)

Otra cosa a tener en cuenta es que los métodos de rasgos son polimórficos, incluso cuando tienen una implementación predeterminada y no se anulan, mientras que los métodos inherentes a los objetos de rasgos son monomórficos (solo hay una función, sin importar qué hay detrás del rasgo). Por ejemplo:

use std::any::TypeId; trait Foo { fn foo_in_trait(&self) where Self: ''static, { println!("{:?}", TypeId::of::<Self>()); } } impl dyn Foo { fn foo_in_impl(&self) { println!("{:?}", TypeId::of::<Self>()); } } impl Foo for u8 {} impl Foo for u16 {} fn main() { let x = Box::new(42u8) as Box<dyn Foo>; x.foo_in_trait(); x.foo_in_impl(); let x = Box::new(42u16) as Box<Foo>; x.foo_in_trait(); x.foo_in_impl(); }

Salida de muestra:

TypeId { t: 10115067289853930363 } TypeId { t: 1357119791063736673 } TypeId { t: 14525050876321463235 } TypeId { t: 1357119791063736673 }

En el método de rasgo, obtenemos la identificación de tipo del tipo subyacente (aquí, u8 o u16 ), por lo que podemos concluir que el tipo de &self variará de un implementador a otro (será &u8 para el implementador u8 y &u16 para el implementador u16 : no es un objeto de rasgo). Sin embargo, en el método inherente, obtenemos el id de tipo de dyn Foo ( + ''static ), por lo que podemos concluir que el tipo de &self es siempre &dyn Foo (un objeto de rasgo).


Sospecho que la razón es muy simple: ¿puede ser anulada o no?

Los implementadores del trait pueden anular un método implementado en un bloque de trait , solo proporciona un valor predeterminado.

Por otro lado, un método implementado en un bloque impl no puede ser anulado.

Si este razonamiento es correcto, entonces el error que obtiene para y.foo_in_impl() es solo una falta de pulido: debería haber funcionado. Vea la respuesta más completa de Francis Gagné sobre la interacción con vidas.