Generics

Sometimes, when writing a function or data type, we may want it to work for multiple types of arguments. Luckily, Rust has a feature that gives us a better way: generics. Generics are called ‘parametric polymorphism’ in type theory, which means that they are types or functions that have multiple forms (‘poly’ is multiple, ‘morph’ is form) over a given parameter (‘parametric’).

Anyway, enough with type theory, let’s check out some generic code. Rust’s standard library provides a type, Option<T>`Option`, that’s generic:

fn main() { enum Option<T> { Some(T), None, } }
enum Option<T> {
    Some(T),
    None,
}

The <T>`part, which you’ve seen a few times before, indicates that this is a generic data type. Inside the declaration of our enum, wherever we see a` part, which you’ve seen a few times before, indicates that this is a generic data type. Inside the declaration of our enum, wherever we see a T`T, we substitute that type for the same type used in the generic. Here’s an example of using`, we substitute that type for the same type used in the generic. Here’s an example of using Option<T>`Option`, with some extra type annotations:

fn main() { let x: Option<i32> = Some(5); }
let x: Option<i32> = Some(5);

In the type declaration, we say Option<i32>`Option. Note how similar this looks to`. Note how similar this looks to Option<T>`Option. So, in this particular`. So, in this particular Option`Option,`, T`Thas the value of` has the value of i32`i32. On the right-hand side of the binding, we do make a`. On the right-hand side of the binding, we do make a Some(T)`Some(T), where`, where T`Tis` is 5`5. Since that’s an`. Since that’s an i32`i32`, the two sides match, and Rust is happy. If they didn’t match, we’d get an error:

fn main() { let x: Option<f64> = Some(5); // error: mismatched types: expected `core::option::Option<f64>`, // found `core::option::Option<_>` (expected f64 but found integral variable) }
let x: Option<f64> = Some(5);
// error: mismatched types: expected `core::option::Option<f64>`,
// found `core::option::Option<_>` (expected f64 but found integral variable)

That doesn’t mean we can’t make Option<T>`Options that hold an`s that hold an f64`f64`! They just have to match up:

fn main() { let x: Option<i32> = Some(5); let y: Option<f64> = Some(5.0f64); }
let x: Option<i32> = Some(5);
let y: Option<f64> = Some(5.0f64);

This is just fine. One definition, multiple uses.

Generics don’t have to only be generic over one type. Consider another type from Rust’s standard library that’s similar, Result<T, E>`Result`:

fn main() { enum Result<T, E> { Ok(T), Err(E), } }
enum Result<T, E> {
    Ok(T),
    Err(E),
}

This type is generic over two types: T`Tand` and E`E. By the way, the capital letters can be any letter you’d like. We could define`. By the way, the capital letters can be any letter you’d like. We could define Result<T, E>`Result` as:

fn main() { enum Result<A, Z> { Ok(A), Err(Z), } }
enum Result<A, Z> {
    Ok(A),
    Err(Z),
}

if we wanted to. Convention says that the first generic parameter should be T`T, for ‘type’, and that we use`, for ‘type’, and that we use E`E` for ‘error’. Rust doesn’t care, however.

The Result<T, E>`Result` type is intended to be used to return the result of a computation, and to have the ability to return an error if it didn’t work out.

Generic functions

We can write functions that take generic types with a similar syntax:

fn main() { fn takes_anything<T>(x: T) { // do something with x } }
fn takes_anything<T>(x: T) {
    // do something with x
}

The syntax has two parts: the <T>`says “this function is generic over one type,` says “this function is generic over one type, T`T”, and the`”, and the x: T`x: Tsays “x has the type` says “x has the type T`T`.”

Multiple arguments can have the same generic type:

fn main() { fn takes_two_of_the_same_things<T>(x: T, y: T) { // ... } }
fn takes_two_of_the_same_things<T>(x: T, y: T) {
    // ...
}

We could write a version that takes multiple types:

fn main() { fn takes_two_things<T, U>(x: T, y: U) { // ... } }
fn takes_two_things<T, U>(x: T, y: U) {
    // ...
}

Generic functions are most useful with ‘trait bounds’, which we’ll cover in the section on traits.

Generic structs

You can store a generic type in a struct`struct` as well:

fn main() { struct Point<T> { x: T, y: T, } let int_origin = Point { x: 0, y: 0 }; let float_origin = Point { x: 0.0, y: 0.0 }; }
struct Point<T> {
    x: T,
    y: T,
}

let int_origin = Point { x: 0, y: 0 };
let float_origin = Point { x: 0.0, y: 0.0 };

Similarly to functions, the <T>`is where we declare the generic parameters, and we then use` is where we declare the generic parameters, and we then use x: T`x: T` in the type declaration, too.