Skip to content

Latest commit

 

History

History
67 lines (55 loc) · 3.14 KB

generics.md

File metadata and controls

67 lines (55 loc) · 3.14 KB

% Using traits for bounds on generics

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:

  • Reusability. Generic functions can be applied to an open-ended collection of types, while giving a clear contract for the functionality those types must provide.

  • Static dispatch and optimization. Each use of a generic function is specialized ("monomorphized") to the particular types implementing the trait bounds, which means that (1) invocations of trait methods are static, direct calls to the implementation and (2) the compiler can inline and otherwise optimize these calls.

  • Inline layout. If a 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.

  • Inference. Since the type parameters to generic functions can usually be inferred, generic functions can help cut down on verbosity in code where explicit conversions or other method calls would usually be necessary. See the overloading/implicits use case below.

  • 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 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:

  • Code size. Specializing generic functions means that the function body is duplicated. The increase in code size must be weighed against the performance benefits of static dispatch.
  • Homogeneous types. This is the other side of the "precise types" coin: if 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.
  • Signature verbosity. Heavy use of generics can bloat function signatures. [Ed. note] This problem may be mitigated by some language improvements; stay tuned.

Favor widespread traits. [FIXME: needs RFC]

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.