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

Al tratar de entender mejor el rasgo Any , vi que tiene un bloque impl para el rasgo en sí mismo . No entiendo el propósito de este constructo, 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 Foo { fn foo_in_impl(&self) { println!("in impl") } } impl Foo for u8 {} fn main() { let x = Box::new(42u8) as Box; x.foo_in_trait(); x.foo_in_impl(); let y = &42u8 as &Foo; y.foo_in_trait(); // y.foo_in_impl(); // error: borrowed value does not live long enough } 

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 se desbloquea, ¡pero no sé lo que es todavía! ^ _ ^

Las secciones de The Rust Programming Language sobre rasgos y objetos de rasgo no hacen ninguna mención de esto. Al buscar en el origen de Rust, parece que solo Any utiliza esta característica en particular. No he visto esto usado en el puñado de cajas donde he visto el código fuente.

Cuando define un rasgo, Rust también define un tipo de objeto rasgo con el mismo nombre. Los objetos de rasgo tienen un parámetro de por vida que designa el más corto de los parámetros de por vida del implementador. Para especificar esa duración, escriba el tipo como Foo + 'a .

Cuando escribe impl Foo { , no está especificando ese parámetro de por vida, y por defecto es 'static . Esta nota del comstackdor en y.foo_in_impl(); statement insinúa eso:

nota: la referencia debe ser válida para la duración estática …

Todo lo que tenemos que hacer para que esto sea más permisivo es escribir una impl genérica sobre cualquier curso de la vida:

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

Ahora, observe que el self argumento en foo_in_impl es un puntero prestado, que tiene un parámetro de por vida propio. El tipo de self , en su forma completa, se ve como &'b (Foo + 'a) (los paréntesis son necesarios debido a la precedencia del operador). A Box pertenece su u8 – no toma prestado nada -, por lo que puede crear un &(Foo + 'static) fuera de este. Por otro lado, &42u8 crea un &'b (Foo + 'a) donde 'a no es 'static , porque 42u8 se coloca en una variable oculta en la stack, y el objeto de rasgo toma prestada esta variable. (Eso en realidad no tiene sentido, sin embargo, u8 no u8 prestado nada, por lo que su implementación de Foo siempre debe ser compatible con Foo + 'static … el hecho de que 42u8 se 42u8 prestado de la stack debería afectar 'b , not 'a .)

Otra cosa a tener en cuenta es que los métodos de rasgo son polimórficos, incluso cuando tienen una implementación predeterminada y no se anulan, mientras que los métodos inherentes en un objeto de rasgo 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::()); } } impl Foo { fn foo_in_impl(&self) { println!("{:?}", TypeId::of::()); } } impl Foo for u8 {} impl Foo for u16 {} fn main() { let x = Box::new(42u8) as Box; x.foo_in_trait(); x.foo_in_impl(); let x = Box::new(42u16) as Box; x.foo_in_trait(); x.foo_in_impl(); } 

Muestra de salida:

 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 tipo de Foo , por lo que podemos concluir que el tipo de &self es siempre &Foo (un objeto rasgo).

Sospecho que el motivo es muy simple: ¿puede ser anulado o no?

Un método implementado en un bloque de trait puede ser anulado por los implementadores del trait , simplemente proporciona un valor predeterminado.

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

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