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 is 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 generic 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.