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
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
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:
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:
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
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
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
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.
You can store a generic type in a struct
`struct` as well:
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.