mirror of
https://github.com/rust-lang-cn/book-cn.git
synced 2025-01-23 23:50:25 +08:00
Small wording edits
This commit is contained in:
parent
479b254803
commit
7dc9bcbe80
@ -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;
|
||||
|
@ -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) {
|
||||
|
Loading…
Reference in New Issue
Block a user