Esto se aborda ahora en la segunda edición de The Rust Programming Language . Sin embargo, profundicemos un poco más.
Comencemos con un ejemplo más simple.
¿Cuándo es apropiado utilizar un método de rasgos?
Hay varias formas de proporcionar enlace tardío :
trait MyTrait {
fn hello_word(&self) -> String;
}
O:
struct MyTrait<T> {
t: T,
hello_world: fn(&T) -> String,
}
impl<T> MyTrait<T> {
fn new(t: T, hello_world: fn(&T) -> String) -> MyTrait<T>;
fn hello_world(&self) -> String {
(self.hello_world)(self.t)
}
}
Sin tener en cuenta ninguna estrategia de implementación / desempeño, ambos extractos anteriores permiten al usuario especificar de manera dinámica cómo hello_worlddebe comportarse.
La única diferencia (semánticamente) es que la traitimplementación garantiza que para un tipo dado que Timplementa el trait, hello_worldsiempre tendrá el mismo comportamiento, mientras que la structimplementación permite tener un comportamiento diferente por instancia.
¡Si usar un método es apropiado o no depende del caso de uso!
¿Cuándo es apropiado utilizar un tipo asociado?
De manera similar a los traitmétodos anteriores, un tipo asociado es una forma de enlace tardío (aunque ocurre en la compilación), lo que permite al usuario traitespecificar para una instancia determinada qué tipo sustituir. No es la única forma (de ahí la pregunta):
trait MyTrait {
type Return;
fn hello_world(&self) -> Self::Return;
}
O:
trait MyTrait<Return> {
fn hello_world(&Self) -> Return;
}
Son equivalentes a la vinculación tardía de los métodos anteriores:
- el primero impone que para un determinado
Selfhay un único Returnasociado
- la segunda, en cambio, permite la implementación
MyTraitde Selfde múltiplesReturn
Qué forma es más apropiada depende de si tiene sentido aplicar la unicidad o no. Por ejemplo:
Deref usa un tipo asociado porque sin unicidad el compilador se volvería loco durante la inferencia
Add usa un tipo asociado porque su autor pensó que dados los dos argumentos habría un tipo de retorno lógico
Como puede ver, si bien Derefes un caso de uso obvio (restricción técnica), el caso de Addes menos claro: ¿tal vez tendría sentido i32 + i32ceder i32o Complex<i32>según el contexto? No obstante, el autor hizo uso de su criterio y decidió que no era necesario sobrecargar el tipo de devolución para las adiciones.
Mi postura personal es que no hay una respuesta correcta. Aún así, más allá del argumento de la unicidad, mencionaría que los tipos asociados facilitan el uso del rasgo ya que disminuyen la cantidad de parámetros que deben especificarse, por lo que, en caso de que los beneficios de la flexibilidad de usar un parámetro de rasgo regular no sean obvios, sugiera comenzar con un tipo asociado.
trait/struct MyTrait/MyStructpermite exactamente unoimpl MyTrait foroimpl MyStruct.trait MyTrait<Return>permite múltiples correos electrónicosimplporque es genérico.Returnpuede ser de cualquier tipo. Las estructuras genéricas son las mismas.Los tipos asociados son un mecanismo de agrupación , por lo que deben usarse cuando tenga sentido agrupar tipos.
El
Graphrasgo introducido en la documentación es un ejemplo de esto. Desea que unGraphsea genérico, pero una vez que tenga un tipo específicoGraph, no desea que los tiposNodeoEdgevaríen más. Un particularGraphno querrá variar esos tipos dentro de una sola implementación y, de hecho, quiere que sean siempre iguales. Están agrupados, o incluso podría decirse asociados .fuente