Small wording edits

This commit is contained in:
Carol (Nichols || Goulding) 2016-09-08 17:34:11 -04:00
parent 479b254803
commit 7dc9bcbe80
2 changed files with 45 additions and 48 deletions

View File

@ -1,8 +1,9 @@
# Generics
One of the core tools a programming gives you is the ability to deal
One of the core tools a programming language gives you is the ability to deal
effectively with duplication. Different kinds of duplication are dealt with in
different ways. Consider a small program that finds the largest number in a list:
different ways. Consider a small program that finds the largest number in a
list:
```rust
let numbers = vec![34, 50, 25, 100, 65];
@ -53,7 +54,7 @@ answer is functions:
```rust
fn largest(numbers: Vec<i32>) {
let mut largest = numbers[0];
for number in numbers {
if largest > number {
largest = number;

View File

@ -1,23 +1,23 @@
# Generics
We've already hinted at generics previously in the book, but never dug into
what exactly they are. You can always recognize when generics are used by
the way that they fit into Rust's syntax:
We've already hinted at generics previously, but never dug into what exactly
they are. You can always recognize when generics are used by the way that they
fit into Rust's syntax: Any time you see angle brackets, `<>`, you're dealing
with generics.
Any time you see angle brackets, `<>`, you're dealing with generics.
The types we've seen before like `Vec<i32>`? That's employing generics. The
proper name for vectors is `Vec<T>`. That `T` is called a *type parameter*, and
it serves a similar function to parameters to functions: you give it some kind
of value, and that determines how it works. In the same way that a function
like `foo(x: i32)` can be called with `foo(5)`, a `Vec<T>` can be created with
a specific type, like `Vec<i32>`.
The types we've seen before like `Vec<i32>`? That's employing generics. The
proper type for vectors is `Vec<T>`. That `T` is called a *type parameter*, and
it serves a similar function to parameters to functions: you fill in the
parameter with a concrete type, and that determines how the overall type works.
In the same way that a function like `foo(x: i32)` can be called with a
specific value such as `foo(5)`, a `Vec<T>` can be created with a specific
type, like `Vec<i32>`.
## Generic data types
Let's dive into generic data types in a bit more detail. We previously learned
about the `Option<T>` type, but we never examined its definition. Let's try to
imagine how we'd write it. First, let's consider an option of a number:
imagine how we'd write it. First, let's consider an `Option` of only a number:
```rust
enum OptionalNumber {
@ -30,7 +30,7 @@ let no_number = OptionalNumber::None;
```
This works just fine for `i32`s. But what if we also wanted to store `f64`s?
Or `String`s? We would have to write this:
Or `String`s? We would have to add code like this for each type we wanted:
```rust
enum OptionalFloatingPointNumber {
@ -42,12 +42,12 @@ let number = OptionalFloatingPointNumber::Some(5.0);
let no_number = OptionalFloatingPointNumber::None;
```
The name is a bit long to drive the point home. With our current knowledge, we
would have to write a unique type for every single kind of option. In other
words, the idea of "an optional value" is a higher-order concept than any
specific type. We want it to work for any type at all.
We've made the enum's name a bit long in order to drive the point home. With our
current knowledge, we would have to write a unique type for every single kind
of option. In other words, the idea of "an optional value" is a higher-order
concept than one specific type. We want it to work for any type at all.
We can do that with generics. In fact, that's how the actual option type works
We can do that with generics. In fact, that's how the actual `Option` type works
in Rust. Let's check out its definition:
```rust
@ -67,7 +67,7 @@ let integer = Option::Some(5);
let float = Option::Some(5.0);
```
We've left in the `Option` bit for consistency with the previous examples, but
We've left in the `Option::` bit for consistency with the previous examples, but
since `Option<T>` is in the prelude, it's not needed:
```rust
@ -75,8 +75,8 @@ let integer = Some(5);
let float = Some(5.0);
```
So, what's up with this syntax. Let's compare our two non-generic `enum`s side by
side:
So, what's up with this syntax. Let's compare our two non-generic `enum`s side
by side:
```text
enum OptionalNumber { enum OptionalFloatingPointNumber {
@ -88,7 +88,7 @@ enum OptionalNumber { enum OptionalFloatingPointNumber {
We have one line that's very close, but different: the `Some` bit. The only
difference is the type of the data, `i32` and `f64`. Just like we can
parameterize arguments to a function by choosing a name, we can parameterize
the type by choosing a name. In this case, `T`. We could choose any identifier
the type by choosing a name, in this case, `T`. We could choose any identifier
here, but traditionally, type parameters follow the same style as types
themselves: CamelCase. In addition, they tend to be short, often one letter.
`T` is the traditional choice, short for 'type'. So let's do that:
@ -103,7 +103,7 @@ enum OptionalNumber { enum OptionalFloatingPointNumber {
We've replaced `i32` and `f64` with `T`. There's one problem, though: we've
*used* `T`, but not defined it. This would be similar to using an argument to
a function without declaring it. We need to tell Rust that we've introduced a
generic parameter. We can do that with the angle brackets, let's try it:
generic parameter. We can do that with the angle brackets; let's try it:
```text
enum OptionalNumber<T> { enum OptionalFloatingPointNumber<T> {
@ -119,24 +119,20 @@ or floating point numbers. So let's give them the same name:
```text
enum Option<T> { enum Option<T> {
Some(T), Some(T),
None, None,
} }
Some(T), Some(T),
None, None,
} }
```
Now they're identical! We've made our type fully generic. Understanding this
process is important, because the compiler actually does the exact opposite of
this when compiling your code. This is called *monomorphization*, and it's why
Rust's generics are extremely efficient. Consider this code:
this when compiling your code. Taking code that is generic over some type and
generating code that is specific for the concrete types that are used with the
generic code is called *monomorphization*, and it's why Rust's generics are
extremely efficient. Consider this code that uses the standard library's
`Option`:
```rust
// This is in the standard library, but we're including it to make the example
// a bit more obvious.
enum Option<T> {
Some(T),
None,
}
let integer = Some(5);
let float = Some(5.0);
```
@ -161,11 +157,10 @@ enum OptionFloat {
let integer = OptionInteger::Some(5);
let float = OptionFloat::Some(5.0);
```
In other words, we can write the non-duplicated form, but Rust will act as
though we wrote the specific type out in each instance. This means that we
pay no runtime cost for using generics; it's just like we copy/pasted
each particular definition.
though we wrote the specific type out in each instance. This means we pay no
runtime cost for using generics; it's just like we copy/pasted each particular
definition.
In a similar fashion, we can use `<>`s with structs as well:
@ -231,7 +226,8 @@ impl Foo {
}
```
It's the same process: if we had these two functions:
We can use the same process to refactor duplicated specific code into code that
uses generics. If we had these two functions:
```text
fn takes_integer(argument: i32) { fn takes_float(argument: f64) {
@ -243,8 +239,8 @@ We'd replace their parameter with `T`:
```text
fn takes_integer(argument: T) { fn takes_float(argument: T) {
// code goes here // code goes here
} }
// code goes here // code goes here
} }
```
Add the `T` parameter to the type parameter list:
@ -255,7 +251,7 @@ fn takes_integer<T>(argument: T) { fn takes_float<T>(argument: T) {
} }
```
And then rename them to be the same:
And then rename them:
```text
fn takes<T>(argument: T) { fn takes<T>(argument: T) {
@ -266,8 +262,8 @@ fn takes<T>(argument: T) { fn takes<T>(argument: T) {
Now they're the same!
There's one problem though. We've got some function _definitions_ that work,
but if we try to do something with our argument, we'll get an error. To see
what we mean here, try out this function:
but if we try to use our argument in the function body, we'll get an error. To
see what we mean here, try compiling this function:
```rust,ignore
fn print<T>(argument: T) {