diff --git a/src/doc/guide.md b/src/doc/guide.md index c00020a8c000..69377b1185b9 100644 --- a/src/doc/guide.md +++ b/src/doc/guide.md @@ -3620,6 +3620,182 @@ guide](http://doc.rust-lang.org/guide-pointers.html#rc-and-arc). # Generics +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? + +```{rust} +enum OptionalInt { + Value(int), + 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 +enum Option { + Some(T), + None, +} +``` + +The `` 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`, +we substitute that type for the same type used in the generic. Here's an +example of using `Option`, with some extra type annotations: + +```{rust} +let x: Option = Some(5i); +``` + +In the type declaration, we say `Option`. Note how similar this looks to +`Option`. So, in this particular `Option`, `T` has the value of `int`. On +the right hand side of the binding, we do make a `Some(T)`, where `T` is `5i`. +Since that's an `int`, the two sides match, and Rust is happy. If they didn't +match, we'd get an error: + +```{rust,ignore} +let x: Option = Some(5i); +// error: mismatched types: expected `core::option::Option` +// but found `core::option::Option` (expected f64 but found int) +``` + +That doesn't mean we can't make `Option`s that hold an `f64`! They just have to +match up: + +```{rust} +let x: Option = Some(5i); +let y: Option = Some(5.0f64); +``` + +This is just fine. One definition, multiple uses. + +Generics don't have to only be generic over one type. Consider Rust's built-in +`Result` type: + +```{rust} +enum Result { + Ok(T), + Err(E), +} +``` + +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` as: + +```{rust} +enum Result { + Ok(H), + Err(N), +} +``` + +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. + +The `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. Here's an example: + +```{rust} +let x: Result = Ok(2.3f64); +let y: Result = Err("There was an error.".to_string()); +``` + +This particular Result will return an `int` if there's a success, and a +`String` if there's a failure. Let's write a function that uses `Result`: + +```{rust} +fn square_root(x: f64) -> Result { + if x < 0.0f64 { return Err("x must be positive!".to_string()); } + + Ok(x * (1.0f64 / 2.0f64)) +} +``` + +We don't want to take the square root of a negative number, so we check +to make sure that's true. If it's not, then we return an `Err`, with a +message. If it's okay, we return an `Ok`, with the answer. + +Why does this matter? Well, remember how `match` does exhaustive matches? +Here's how this function gets used: + +```{rust} +# fn square_root(x: f64) -> Result { +# if x < 0.0f64 { return Err("x must be positive!".to_string()); } +# Ok(x * (1.0f64 / 2.0f64)) +# } +let x = square_root(25.0f64); + +match x { + Ok(x) => println!("The square root of 25 is {}", x), + Err(msg) => println!("Error: {}", msg), +} +``` + +The `match enforces that we handle the `Err` case. In addition, because the +answer is wrapped up in an `Ok`, we can't just use the result without doing +the match: + +```{rust,ignore} +let x = square_root(25.0f64); +println!("{}", x + 2.0f64); // error: binary operation `+` cannot be applied + // to type `core::result::Result` +``` + +This function is great, but there's one other problem: it only works for 64 bit +floating point values. What if we wanted to handle 32 bit floating point as +well? We'd have to write this: + +```{rust} +fn square_root32(x: f32) -> Result { + if x < 0.0f32 { return Err("x must be positive!".to_string()); } + + Ok(x * (1.0f32 / 2.0f32)) +} +``` + +Bummer. What we need is a **generic function**. Luckily, we can write one! +However, it won't _quite_ work yet. Before we get into that, let's talk syntax. +A generic version of `square_root` would look something like this: + +```{rust,ignore} +fn square_root(x: T) -> Result { + if x < 0.0 { return Err("x must be positive!".to_string()); } + + Ok(x * (1.0 / 2.0)) +} +``` + +Just like how we had `Option`, we use a similar syntax for `square_root`. +We can then use `T` inside the rest of the signature: `x` has type `T`, and half +of the `Result` has type `T`. However, if we try to compile that example, we'll get +an error: + +```{notrust,ignore} +error: binary operation `<` cannot be applied to type `T` +``` + +Because `T` can be _any_ type, it may be a type that doesn't implement `<`, +and therefore, the first line would be wrong. What do we do? + +To fix this example, we need to learn about another Rust feature: traits. + # Traits # Operators and built-in Traits