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_world
debe comportarse.
La única diferencia (semánticamente) es que la trait
implementación garantiza que para un tipo dado que T
implementa el trait
, hello_world
siempre tendrá el mismo comportamiento, mientras que la struct
implementació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 trait
métodos anteriores, un tipo asociado es una forma de enlace tardío (aunque ocurre en la compilación), lo que permite al usuario trait
especificar 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
Self
hay un único Return
asociado
- la segunda, en cambio, permite la implementación
MyTrait
de Self
de 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 Deref
es un caso de uso obvio (restricción técnica), el caso de Add
es menos claro: ¿tal vez tendría sentido i32 + i32
ceder i32
o 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/MyStruct
permite exactamente unoimpl MyTrait for
oimpl MyStruct
.trait MyTrait<Return>
permite múltiples correos electrónicosimpl
porque es genérico.Return
puede 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
Graph
rasgo introducido en la documentación es un ejemplo de esto. Desea que unGraph
sea genérico, pero una vez que tenga un tipo específicoGraph
, no desea que los tiposNode
oEdge
varíen más. Un particularGraph
no 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