Isso agora é abordado na segunda edição de The Rust Programming Language . No entanto, vamos mergulhar um pouco mais além.
Vamos começar com um exemplo mais simples.
Quando é apropriado usar um método de característica?
Existem várias maneiras de fornecer vinculação tardia :
trait MyTrait {
fn hello_word(&self) -> String;
}
Ou:
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)
}
}
Desconsiderando qualquer estratégia de implementação / desempenho, os dois trechos acima permitem que o usuário especifique de forma dinâmica como hello_world
deve se comportar.
A única diferença (semanticamente) é que a trait
implementação garante que, para um determinado tipo que T
implementa o trait
, hello_world
sempre terá o mesmo comportamento, enquanto ostruct
implementação permite ter um comportamento diferente por instância.
Se usar um método é apropriado ou não depende do caso de uso!
Quando é apropriado usar um tipo associado?
De maneira semelhante aos trait
métodos acima, um tipo associado é uma forma de vinculação tardia (embora ocorra na compilação), permitindo que o usuário do trait
especifique para uma determinada instância que tipo substituir. Não é a única maneira (daí a questão):
trait MyTrait {
type Return;
fn hello_world(&self) -> Self::Return;
}
Ou:
trait MyTrait<Return> {
fn hello_world(&Self) -> Return;
}
São equivalentes à ligação tardia dos métodos acima:
- o primeiro reforça que para um dado
Self
existe um único Return
associado
- o segundo, em vez disso, permite a implementação
MyTrait
de Self
para múltiplosReturn
Qual forma é mais apropriada depende se faz sentido impor a unicidade ou não. Por exemplo:
Deref
usa um tipo associado porque sem unicidade o compilador enlouqueceria durante a inferência
Add
usa um tipo associado porque seu autor pensou que dados os dois argumentos haveria um tipo de retorno lógico
Como você pode ver, embora Deref
seja um caso de uso óbvio (restrição técnica), o caso de Add
é menos claro: talvez faria sentido i32 + i32
render um i32
ou Complex<i32>
dependendo do contexto? No entanto, o autor exerceu seu julgamento e decidiu que não era necessário sobrecarregar o tipo de retorno para acréscimos.
Minha posição pessoal é que não existe uma resposta certa. Ainda assim, além do argumento da unicidade, eu mencionaria que os tipos associados tornam o uso do traço mais fácil, pois diminuem o número de parâmetros que devem ser especificados, então, caso os benefícios da flexibilidade de usar um parâmetro de traço regular não sejam óbvios, I sugerir começar com um tipo associado.
trait/struct MyTrait/MyStruct
permite exatamente umimpl MyTrait for
ouimpl MyStruct
.trait MyTrait<Return>
permite váriosimpl
s porque é genérico.Return
pode ser de qualquer tipo. As estruturas genéricas são iguais.Os tipos associados são um mecanismo de agrupamento , portanto, devem ser usados quando fizer sentido agrupar os tipos.
O
Graph
traço introduzido na documentação é um exemplo disso. Você deseja que umGraph
seja genérico, mas uma vez que tenha um tipo específico deGraph
, você não quer que os tiposNode
ouEdge
variem mais. Um indivíduoGraph
não vai querer variar esses tipos em uma única implementação e, na verdade, deseja que sejam sempre os mesmos. Eles estão agrupados ou, pode-se dizer, associados .fonte