name keywords google etiquetas ejemplos description content types rust idiomatic

types - keywords - meta tags generator



¿Cuándo es apropiado usar un tipo asociado versus un tipo genérico? (2)

En esta pregunta , surgió un problema que podría resolverse cambiando un intento de usar un parámetro de tipo genérico en un tipo asociado. Eso provocó la pregunta "¿Por qué un tipo asociado es más apropiado aquí?", Lo que me hizo querer saber más.

El RFC que introdujo los tipos asociados dice:

Este RFC aclara la coincidencia de rasgos mediante:

  • Tratar todos los parámetros de tipo de rasgo como tipos de entrada , y
  • Proporcionar tipos asociados, que son tipos de salida .

El RFC usa una estructura gráfica como un ejemplo motivador, y esto también se usa en la documentación , pero admito que no aprecio completamente los beneficios de la versión de tipo asociada sobre la versión con parámetros de tipo. Lo principal es que el método de distance no necesita preocuparse por el tipo de Edge . Esto es bueno, pero parece una razón un poco superficial para tener tipos asociados.

He encontrado que los tipos asociados son bastante intuitivos para usar en la práctica, pero me cuesta mucho decidir cuándo y dónde debo usarlos en mi propia API.

Al escribir código, ¿cuándo debo elegir un tipo asociado sobre un parámetro de tipo genérico y cuándo debo hacer lo contrario?


Esto se menciona ahora en la segunda edición de The Rust Programming Language . Sin embargo, vamos a sumergirnos un poco más.

Comencemos con un ejemplo más simple.

¿Cuándo es apropiado usar un método de rasgo?

Hay varias formas de proporcionar un 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 / rendimiento, ambos extractos anteriores permiten al usuario especificar de manera dinámica cómo debe comportarse hello_world .

La única diferencia (semánticamente) es que la implementación del trait garantiza que para un tipo T dado que implementa el trait , hello_world siempre tendrá el mismo comportamiento, mientras que la implementación de la struct 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 usar un tipo asociado?

De manera similar a los métodos de trait anteriores, un tipo asociado es una forma de enlace tardío (aunque ocurre en la compilación), lo que permite al usuario del 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 unión tardía de los métodos anteriores:

  • el primero hace cumplir que para un Self dado hay un solo Return asociado
  • el segundo, en cambio, permite implementar MyTrait for Self para Return múltiple

La forma más apropiada depende de si tiene sentido imponer 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, mientras Deref es un caso de uso obvio (restricción técnica), el caso de Add es menos claro: ¿quizás tendría sentido que i32 + i32 produzca i32 o Complex<i32> dependiendo del contexto? No obstante, el autor ejerció su juicio y decidió que no era necesario sobrecargar el tipo de devolución para 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 el número 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, yo sugiera comenzar con un tipo asociado.


Los tipos asociados son un mecanismo de agrupación , por lo que deben usarse cuando tenga sentido agrupar los tipos.

El rasgo Graph introducido en la documentación es un ejemplo de esto. Desea que un Graph sea ​​genérico, pero una vez que tiene un tipo específico de Graph , ya no desea que los tipos de Node o Edge varíen más. Un Graph particular no va a querer variar esos tipos dentro de una sola implementación y, de hecho, quiere que siempre sean los mismos. Están agrupados, o incluso se podría decir que están asociados .