Generics

Generics are a concept to refer to many types at once, and to be polymorphic over them. Generics use the angle bracket syntax (<T>), which is common in many C-like languages.

An identifier that refers to many types is called a type parameter, a type variable or simply a generic type. A type, trait, function or impl block with a type parameter is called generic. Strictly speaking, types, traits, functions and impl blocks with a lifetime are also generic. For functions, this isn't always visible, since lifetimes are often elided.

ExampleEdit

This example illustrates where generics can be used. The identifier T is used as type parameter, which is common practice in Rust.
// generic trait
trait Foo<T> {}

// generic types
struct MyStruct<T>(T);
enum MyEnum<T> { Value(T) }
union MyUnion<T> { field: T };
type MyType<T> = MyEnum<MyUnion<T>>;

// generic implementations
impl<T> MyStruct<T> {}
impl<T> Foo<T> for (T, T) {}

// generic function
fn my_function<T: Foo>(foo: T) {}
Each type parameter must be declared before it can be used:
struct MyStruct<T> { // declaration of T
    value: T,        // usage of T
}

impl<T> MyTrait for MyStruct<T> {}
//   ^~ declaration of T     ^~ usage of T

trait MyTrait {
    fn my<T>(value: MyStruct<T>);
    //    ^~ declaration     ^~ usage
}

BoundsEdit

Type parameters and lifetimes can be bounded by traits and by lifetimes. Bounds appear after a colon (:) after the parameter and are separated with a + sign. It is possible to move bounds to a where clause:
fn foo<'a, T: SomeTrait + OtherTrait + 'a>(value: &'a T) {}

struct Foo<T: Clone + ?Sized>(T);

// this can be written as
fn foo<'a, T>(value: &'a T)
where
    T: SomeTrait + OtherTrait + 'a
{}

struct Foo<T>(T)
where
    T: Clone + ?Sized;

Lifetimes on genericsEdit

Each type parameter has an implicit lifetime bound, since it can potentially be used for a type which borrows data. This can be prevented by adding an explicit 'static bound, which means that the type must not borrow values, unless these borrows have the 'static lifetime:

struct Foo<T: 'static>(T);

let f1 = Foo(5);   // works, since nothing is borrowed
let f2 = Foo("hi") // works, since string literals are 'static
let f3 = Foo(&f1); // ERROR! &f1 doesn't have the 'static lifetime

When omitting the lifetime, it is usually inferred correctly, so this doesn't need to be considered except in rare edge cases.

UsageEdit

When using a generic trait or type, its type parameters must be substituted with concrete types or with new type parameters.

To substitute a generic parameter on a type or trait, wrap the list of parameters in less than / greater than signs: Foo<u32>. To make the compiler infer it, write an underscore in place of the parameter: Foo<_>, though this is only allowed in type ascriptions in a function body, since type inference is not allowed elsewhere:

impl Trait<_> for Foo<_> {} //~ERROR: placeholder generics not allowed here

fn use_trait() {
    let t: dyn Trait<_> = other_function(); // This is allowed
    let u: dyn Trait = other_function(); //~ERROR: missing parameters
}

In generic function calls and other generics in a value context, the syntax is different:

  • Omitting the list of generic parameters entirely is allowed, if enough information exists inside the function to infer the types
  • The list of generics has to be written as the so-called turbofish, which two colons before the less-than sign: some_function::<u32>()
// Example function
fn create<T: Default>() -> u32 {
    T::default()
}

// Example usage
fn main() {
    // This version uses a "turbofish"
    let a = create::<u32>();
    // This version uses type ascription to force the compiler to infer the type we want
    let b: u32 = create();
    // Both calls accomplish the same thing
    assert_eq!(a, b);
}