TRPL edits: generics

This commit is contained in:
Steve Klabnik 2015-04-18 17:37:49 -04:00
parent 1c48227b3c
commit 14af25797f

View file

@ -1,31 +1,13 @@
% Generics % Generics
Sometimes, when writing a function or data type, we may want it to work for Sometimes, when writing a function or data type, we may want it to work for
multiple types of arguments. For example, remember our `OptionalInt` type? 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).
```{rust} Anyway, enough with type theory, lets check out some generic code. Rusts
enum OptionalInt { standard library provides a type, `Option<T>`, thats generic:
Value(i32),
Missing,
}
```
If we wanted to also have an `OptionalFloat64`, we would need a new enum:
```{rust}
enum OptionalFloat64 {
Valuef64(f64),
Missingf64,
}
```
This is really unfortunate. 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 declarations, let's check out the generic form
of `OptionalInt`. It is actually provided by Rust itself, and looks like this:
```rust ```rust
enum Option<T> { enum Option<T> {
@ -34,41 +16,40 @@ enum Option<T> {
} }
``` ```
The `<T>` part, which you've seen a few times before, indicates that this is The `<T>` part, which youve seen a few times before, indicates that this is
a generic data type. Inside the declaration of our enum, wherever we see a `T`, a generic data type. Inside the declaration of our enum, wherever we see a `T`,
we substitute that type for the same type used in the generic. Here's an we substitute that type for the same type used in the generic. Heres an
example of using `Option<T>`, with some extra type annotations: example of using `Option<T>`, with some extra type annotations:
```{rust} ```rust
let x: Option<i32> = Some(5); let x: Option<i32> = Some(5);
``` ```
In the type declaration, we say `Option<i32>`. Note how similar this looks to In the type declaration, we say `Option<i32>`. Note how similar this looks to
`Option<T>`. So, in this particular `Option`, `T` has the value of `i32`. On `Option<T>`. So, in this particular `Option`, `T` has the value of `i32`. On
the right-hand side of the binding, we do make a `Some(T)`, where `T` is `5`. the right-hand side of the binding, we do make a `Some(T)`, where `T` is `5`.
Since that's an `i32`, the two sides match, and Rust is happy. If they didn't Since thats an `i32`, the two sides match, and Rust is happy. If they didnt
match, we'd get an error: match, wed get an error:
```{rust,ignore} ```rust,ignore
let x: Option<f64> = Some(5); let x: Option<f64> = Some(5);
// error: mismatched types: expected `core::option::Option<f64>`, // error: mismatched types: expected `core::option::Option<f64>`,
// found `core::option::Option<_>` (expected f64 but found integral variable) // found `core::option::Option<_>` (expected f64 but found integral variable)
``` ```
That doesn't mean we can't make `Option<T>`s that hold an `f64`! They just have to That doesnt mean we cant make `Option<T>`s that hold an `f64`! They just have
match up: to match up:
```{rust} ```rust
let x: Option<i32> = Some(5); let x: Option<i32> = Some(5);
let y: Option<f64> = Some(5.0f64); let y: Option<f64> = Some(5.0f64);
``` ```
This is just fine. One definition, multiple uses. This is just fine. One definition, multiple uses.
Generics don't have to only be generic over one type. Consider Rust's built-in Generics dont have to only be generic over one type. Consider another type from Rusts standard library thats similar, `Result<T, E>`:
`Result<T, E>` type:
```{rust} ```rust
enum Result<T, E> { enum Result<T, E> {
Ok(T), Ok(T),
Err(E), Err(E),
@ -76,9 +57,9 @@ enum Result<T, E> {
``` ```
This type is generic over _two_ types: `T` and `E`. By the way, the capital letters This type is generic over _two_ types: `T` and `E`. By the way, the capital letters
can be any letter you'd like. We could define `Result<T, E>` as: can be any letter youd like. We could define `Result<T, E>` as:
```{rust} ```rust
enum Result<A, Z> { enum Result<A, Z> {
Ok(A), Ok(A),
Err(Z), Err(Z),
@ -86,7 +67,58 @@ enum Result<A, Z> {
``` ```
if we wanted to. Convention says that the first generic parameter should be if we wanted to. Convention says that the first generic parameter should be
`T`, for 'type,' and that we use `E` for 'error.' Rust doesn't care, however. `T`, for type, and that we use `E` for error. Rust doesnt care, however.
The `Result<T, E>` type is intended to be used to return the result of a The `Result<T, E>` 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. computation, and to have the ability to return an error if it didnt work out.
## Generic functions
We can write functions that take generic types with a similar syntax:
```rust
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, `T`”, and the `x: T` says “x has the type `T`.”
Multiple arguments can have the same generic type:
```rust
fn takes_two_of_the_same_things<T>(x: T, y: T) {
// ...
}
```
We could write a version that takes multiple types:
```rust
fn takes_two_things<T, U>(x: T, y: U) {
// ...
}
```
Generic functions are most useful with trait bounds, which well cover in the
[section on traits][traits].
[traits]: traits.html
## Generic structs
You can store a generic type in a `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 `x: T` in the type declaration, too.