To quote a very old RFC :
https://rust-lang.github.io/rfcs/0195-associated-items.html:
As the graph example above illustrates, associated types do not increase the expressiveness of traits per se , because you can always use extra type parameters to a trait instead. However, associated types provide several engineering benefits:
Readability and scalability Associated types make it possible to abstract over a whole family of types at once, without having to separately name each of them. This improves the readability of generic code (like the distance
function above). It also makes generics more "scalable": traits can incorporate additional associated types without imposing an extra burden on clients that don't care about those types.In today's Rust, by contrast, adding additional generic parameters to a trait often feels like a very "heavyweight" move.
Ease of refactoring/evolution Because users of a trait do not have to separately parameterize over its associated types, new associated types can be added without breaking all existing client code.In today's Rust, by contrast, associated types can only be added by adding more type parameters to a trait, which breaks all code mentioning the trait.
Type parameters to traits can either be "inputs" or "outputs":
Inputs . An "input" type parameter is used to determine which impl
to use.
Outputs . An "output" type parameter is uniquely determined by the impl
, but plays no role in selecting the impl
.
Input and output types play an important role for type inference and trait coherence rules, which is described in more detail later on.
An example
trait Generics<A, B, C, D, E> {
fn other_stuff();
}
trait Assocs {
type A;
type B;
type C;
type D;
type E;
fn other_stuff();
}
fn call_other_stuff_generics<A, B, C, D, E, T> ()
where
T : Generics<A, B, C, D, E>,
{
T::other_stuff()
}
fn call_other_stuff_assocs<T : Assocs> ()
{
T::other_stuff()
}
As well as:
https://rust-lang.github.io/rfcs/0195-associated-items.html:
Finally, output type parameters can be inferred/resolved as soon as there is a matching impl
based on the input type parameters. Because of the coherence property above, there can be at most one.
On the other hand, even if there is only one applicable impl
, type inference is not allowed to infer the input type parameters from it. This restriction makes it possible to ensure crate concatenation : adding another crate may add impl
s for a given trait, and if type inference depended on the absence of such impl
s, importing a crate could break existing code.
In practice, these inference benefits can be quite valuable. For example, in the Add
trait given at the beginning of this RFC, the Sum
output type is immediately known once the input types are known, which can avoid the need for type annotations.
Note that sometimes type inference can be too smart for its own good, so the "importing a crate could break existing code" is a real issue in current Rust.