The most widespread use of traits is for writing generic functions or types. For
example, the following signature describes a function for consuming any iterator
yielding items of type A to produce a collection of A:
fn from_iter<T: Iterator<A>>(iterator: T) -> SomeCollection<A>
Here, the Iterator trait specifies an interface that a type T must
explicitly implement to be used by this generic function.
Pros:
struct and enum type is generic over some type
parameter T, values of type T will be laid out inline in the
struct/enum, without any indirection.Precise types. Because generics give a name to the specific type implementing a trait, it is possible to be precise about places where that exact type is required or produced. For example, a function
fn main() { fn binary<T: Trait>(x: T, y: T) -> T }fn binary<T: Trait>(x: T, y: T) -> T
is guaranteed to consume and produce elements of exactly the same type T; it
cannot be invoked with parameters of different types that both implement
Trait.
Cons:
T is a type parameter, it stands for a single actual type. So for example
a Vec<T> contains elements of a single concrete type (and, indeed, the
vector representation is specialized to lay these out in line). Sometimes
heterogeneous collections are useful; see
trait objects below.Generic types are a form of abstraction, which entails a mental indirection: if
a function takes an argument of type T bounded by Trait, clients must first
think about the concrete types that implement Trait to understand how and when
the function is callable.
To keep the cost of abstraction low, favor widely-known traits. Whenever possible, implement and use traits provided as part of the standard library. Do not introduce new traits for generics lightly; wait until there are a wide range of types that can implement the type.