Skip to content

Commit 5bd7a78

Browse files
committed
rollup merge of #19625: mrhota/guide_traits
Nothing major. Clarification, copy-editing, typographical and grammatical consistency
2 parents 4b0e084 + daed54d commit 5bd7a78

File tree

1 file changed

+110
-122
lines changed

1 file changed

+110
-122
lines changed

src/doc/guide.md

Lines changed: 110 additions & 122 deletions
Original file line numberDiff line numberDiff line change
@@ -4769,13 +4769,13 @@ enum OptionalFloat64 {
47694769
}
47704770
```
47714771
4772-
This is really unfortunate. Luckily, Rust has a feature that gives us a better
4773-
way: generics. Generics are called **parametric polymorphism** in type theory,
4774-
which means that they are types or functions that have multiple forms ("poly"
4775-
is multiple, "morph" is form) over a given parameter ("parametric").
4772+
Such repetition is unfortunate. Luckily, Rust has a feature that gives us a
4773+
better way: **generics**. Generics are called **parametric polymorphism** in
4774+
type theory, which means that they are types or functions that have multiple
4775+
forms over a given parameter ("parametric").
47764776
4777-
Anyway, enough with type theory declarations, let's check out the generic form
4778-
of `OptionalInt`. It is actually provided by Rust itself, and looks like this:
4777+
Let's see how generics help us escape `OptionalInt`. `Option` is already
4778+
provided in Rust's standard library and looks like this:
47794779
47804780
```rust
47814781
enum Option<T> {
@@ -4784,25 +4784,27 @@ enum Option<T> {
47844784
}
47854785
```
47864786
4787-
The `<T>` part, which you've seen a few times before, indicates that this is
4788-
a generic data type. Inside the declaration of our enum, wherever we see a `T`,
4789-
we substitute that type for the same type used in the generic. Here's an
4790-
example of using `Option<T>`, with some extra type annotations:
4787+
The `<T>` part, which you've seen a few times before, indicates that this is a
4788+
generic data type. `T` is called a **type parameter**. When we create instances
4789+
of `Option`, we need to provide a concrete type in place of the type
4790+
parameter. For example, if we wanted something like our `OptionalInt`, we would
4791+
need to instantiate an `Option<int>`. Inside the declaration of our enum,
4792+
wherever we see a `T`, we replace it with the type specified (or inferred by the
4793+
the compiler).
47914794
47924795
```{rust}
47934796
let x: Option<int> = Some(5i);
47944797
```
47954798
4796-
In the type declaration, we say `Option<int>`. Note how similar this looks to
4797-
`Option<T>`. So, in this particular `Option`, `T` has the value of `int`. On
4798-
the right-hand side of the binding, we do make a `Some(T)`, where `T` is `5i`.
4799-
Since that's an `int`, the two sides match, and Rust is happy. If they didn't
4800-
match, we'd get an error:
4799+
In this particular `Option`, `T` has the value of `int`. On the right-hand side
4800+
of the binding, we do make a `Some(T)`, where `T` is `5i`. Since that's an
4801+
`int`, the two sides match, and Rust is happy. If they didn't match, we'd get an
4802+
error:
48014803
48024804
```{rust,ignore}
48034805
let x: Option<f64> = Some(5i);
4804-
// error: mismatched types: expected `core::option::Option<f64>`
4805-
// but found `core::option::Option<int>` (expected f64 but found int)
4806+
// error: mismatched types: expected `core::option::Option<f64>`,
4807+
// found `core::option::Option<int>` (expected f64, found int)
48064808
```
48074809
48084810
That doesn't mean we can't make `Option<T>`s that hold an `f64`! They just have to
@@ -4813,8 +4815,6 @@ let x: Option<int> = Some(5i);
48134815
let y: Option<f64> = Some(5.0f64);
48144816
```
48154817
4816-
This is just fine. One definition, multiple uses.
4817-
48184818
Generics don't have to only be generic over one type. Consider Rust's built-in
48194819
`Result<T, E>` type:
48204820
@@ -4835,20 +4835,20 @@ enum Result<H, N> {
48354835
}
48364836
```
48374837
4838-
if we wanted to. Convention says that the first generic parameter should be
4839-
`T`, for 'type,' and that we use `E` for 'error'. Rust doesn't care, however.
4838+
Convention says that the first generic parameter should be `T`, for "type," and
4839+
that we use `E` for "error."
48404840
4841-
The `Result<T, E>` type is intended to
4842-
be used to return the result of a computation, and to have the ability to
4843-
return an error if it didn't work out. Here's an example:
4841+
The `Result<T, E>` type is intended to be used to return the result of a
4842+
computation and to have the ability to return an error if it didn't work
4843+
out. Here's an example:
48444844
48454845
```{rust}
48464846
let x: Result<f64, String> = Ok(2.3f64);
48474847
let y: Result<f64, String> = Err("There was an error.".to_string());
48484848
```
48494849
4850-
This particular Result will return an `f64` if there's a success, and a
4851-
`String` if there's a failure. Let's write a function that uses `Result<T, E>`:
4850+
This particular `Result` will return an `f64` upon success and a `String` if
4851+
there's a failure. Let's write a function that uses `Result<T, E>`:
48524852
48534853
```{rust}
48544854
fn inverse(x: f64) -> Result<f64, String> {
@@ -4858,17 +4858,18 @@ fn inverse(x: f64) -> Result<f64, String> {
48584858
}
48594859
```
48604860
4861-
We don't want to take the inverse of zero, so we check to make sure that we
4862-
weren't passed zero. If we were, then we return an `Err`, with a message. If
4863-
it's okay, we return an `Ok`, with the answer.
4861+
We want to indicate that `inverse(0.0f64)` is undefined or is an erroneous usage
4862+
of the function, so we check to make sure that we weren't passed zero. If we
4863+
were, we return an `Err` with a message. If it's okay, we return an `Ok` with
4864+
the answer.
48644865
48654866
Why does this matter? Well, remember how `match` does exhaustive matches?
48664867
Here's how this function gets used:
48674868
48684869
```{rust}
48694870
# fn inverse(x: f64) -> Result<f64, String> {
4870-
# if x == 0.0f64 { return Err("x cannot be zero!".to_string()); }
4871-
# Ok(1.0f64 / x)
4871+
# if x == 0.0f64 { return Err("x cannot be zero!".to_string()); }
4872+
# Ok(1.0f64 / x)
48724873
# }
48734874
let x = inverse(25.0f64);
48744875
@@ -4889,8 +4890,8 @@ println!("{}", x + 2.0f64); // error: binary operation `+` cannot be applied
48894890
```
48904891
48914892
This function is great, but there's one other problem: it only works for 64 bit
4892-
floating point values. What if we wanted to handle 32 bit floating point as
4893-
well? We'd have to write this:
4893+
floating point values. If we wanted to handle 32 bit floating point values we'd
4894+
have to write this:
48944895
48954896
```{rust}
48964897
fn inverse32(x: f32) -> Result<f32, String> {
@@ -4900,9 +4901,9 @@ fn inverse32(x: f32) -> Result<f32, String> {
49004901
}
49014902
```
49024903
4903-
Bummer. What we need is a **generic function**. Luckily, we can write one!
4904-
However, it won't _quite_ work yet. Before we get into that, let's talk syntax.
4905-
A generic version of `inverse` would look something like this:
4904+
What we need is a **generic function**. We can do that with Rust! However, it
4905+
won't _quite_ work yet. We need to talk about syntax. A first attempt at a
4906+
generic version of `inverse` might look something like this:
49064907
49074908
```{rust,ignore}
49084909
fn inverse<T>(x: T) -> Result<T, String> {
@@ -4912,24 +4913,34 @@ fn inverse<T>(x: T) -> Result<T, String> {
49124913
}
49134914
```
49144915
4915-
Just like how we had `Option<T>`, we use a similar syntax for `inverse<T>`.
4916-
We can then use `T` inside the rest of the signature: `x` has type `T`, and half
4917-
of the `Result` has type `T`. However, if we try to compile that example, we'll get
4918-
an error:
4916+
Just like how we had `Option<T>`, we use a similar syntax for `inverse<T>`. We
4917+
can then use `T` inside the rest of the signature: `x` has type `T`, and half of
4918+
the `Result` has type `T`. However, if we try to compile that example, we'll get
4919+
some errors:
49194920
49204921
```text
49214922
error: binary operation `==` cannot be applied to type `T`
4923+
if x == 0.0 { return Err("x cannot be zero!".to_string()); }
4924+
^~~~~~~~
4925+
error: mismatched types: expected `_`, found `T` (expected floating-point variable, found type parameter)
4926+
Ok(1.0 / x)
4927+
^
4928+
error: mismatched types: expected `core::result::Result<T, collections::string::String>`, found `core::result::Result<_, _>` (expected type parameter, found floating-point variable)
4929+
Ok(1.0 / x)
4930+
^~~~~~~~~~~
49224931
```
49234932
4924-
Because `T` can be _any_ type, it may be a type that doesn't implement `==`,
4925-
and therefore, the first line would be wrong. What do we do?
4933+
The problem is that `T` is unconstrained: it can be _any_ type. It could be a
4934+
`String`, and the expression `1.0 / x` has no meaning if `x` is a `String`. It
4935+
may be a type that doesn't implement `==`, and the first line would be
4936+
wrong. What do we do?
49264937
4927-
To fix this example, we need to learn about another Rust feature: traits.
4938+
To fix this example, we need to learn about another Rust feature: **traits**.
49284939
49294940
# Traits
49304941
4931-
Do you remember the `impl` keyword, used to call a function with method
4932-
syntax?
4942+
Our discussion of **traits** begins with the `impl` keyword. We used it before
4943+
to specify methods.
49334944
49344945
```{rust}
49354946
struct Circle {
@@ -4945,8 +4956,8 @@ impl Circle {
49454956
}
49464957
```
49474958
4948-
Traits are similar, except that we define a trait with just the method
4949-
signature, then implement the trait for that struct. Like this:
4959+
We define a trait in terms of its methods. We then `impl` a trait `for` a type
4960+
(or many types).
49504961
49514962
```{rust}
49524963
struct Circle {
@@ -4966,19 +4977,18 @@ impl HasArea for Circle {
49664977
}
49674978
```
49684979
4969-
As you can see, the `trait` block looks very similar to the `impl` block,
4970-
but we don't define a body, just a type signature. When we `impl` a trait,
4971-
we use `impl Trait for Item`, rather than just `impl Item`.
4980+
The `trait` block defines only type signatures. When we `impl` a trait, we use
4981+
`impl Trait for Item`, rather than just `impl Item`.
49724982
4973-
So what's the big deal? Remember the error we were getting with our generic
4974-
`inverse` function?
4983+
The first of the three errors we got with our generic `inverse` function was
4984+
this:
49754985
49764986
```text
49774987
error: binary operation `==` cannot be applied to type `T`
49784988
```
49794989
4980-
We can use traits to constrain our generics. Consider this function, which
4981-
does not compile, and gives us a similar error:
4990+
We can use traits to constrain generic type parameters. Consider this function,
4991+
which does not compile, and gives us a similar error:
49824992
49834993
```{rust,ignore}
49844994
fn print_area<T>(shape: T) {
@@ -4993,8 +5003,9 @@ error: type `T` does not implement any method in scope named `area`
49935003
```
49945004
49955005
Because `T` can be any type, we can't be sure that it implements the `area`
4996-
method. But we can add a **trait constraint** to our generic `T`, ensuring
4997-
that it does:
5006+
method. But we can add a **trait constraint** to our generic `T`, ensuring that
5007+
we can only compile the function if it's called with types which `impl` the
5008+
`HasArea` trait:
49985009
49995010
```{rust}
50005011
# trait HasArea {
@@ -5005,9 +5016,9 @@ fn print_area<T: HasArea>(shape: T) {
50055016
}
50065017
```
50075018
5008-
The syntax `<T: HasArea>` means `any type that implements the HasArea trait`.
5009-
Because traits define function type signatures, we can be sure that any type
5010-
which implements `HasArea` will have an `.area()` method.
5019+
The syntax `<T: HasArea>` means "any type that implements the HasArea trait."
5020+
Because traits define method signatures, we can be sure that any type which
5021+
implements `HasArea` will have an `area` method.
50115022
50125023
Here's an extended example of how this works:
50135024
@@ -5105,55 +5116,22 @@ impl HasArea for int {
51055116
It is considered poor style to implement methods on such primitive types, even
51065117
though it is possible.
51075118
5108-
This may seem like the Wild West, but there are two other restrictions around
5109-
implementing traits that prevent this from getting out of hand. First, traits
5110-
must be `use`d in any scope where you wish to use the trait's method. So for
5111-
example, this does not work:
5112-
5113-
```{rust,ignore}
5114-
mod shapes {
5115-
use std::f64::consts;
5116-
5117-
trait HasArea {
5118-
fn area(&self) -> f64;
5119-
}
5120-
5121-
struct Circle {
5122-
x: f64,
5123-
y: f64,
5124-
radius: f64,
5125-
}
5126-
5127-
impl HasArea for Circle {
5128-
fn area(&self) -> f64 {
5129-
consts::PI * (self.radius * self.radius)
5130-
}
5131-
}
5132-
}
5133-
5134-
fn main() {
5135-
let c = shapes::Circle {
5136-
x: 0.0f64,
5137-
y: 0.0f64,
5138-
radius: 1.0f64,
5139-
};
5119+
## Scoped Method Resolution and Orphan `impl`s
51405120
5141-
println!("{}", c.area());
5142-
}
5143-
```
5144-
5145-
Now that we've moved the structs and traits into their own module, we get an
5146-
error:
5121+
There are two restrictions for implementing traits that prevent this from
5122+
getting out of hand.
51475123
5148-
```text
5149-
error: type `shapes::Circle` does not implement any method in scope named `area`
5150-
```
5124+
1. **Scope-based Method Resolution**: Traits must be `use`d in any scope where
5125+
you wish to use the trait's methods
5126+
2. **No Orphan `impl`s**: Either the trait or the type you're writing the `impl`
5127+
for must be inside your crate.
51515128
5152-
If we add a `use` line right above `main` and make the right things public,
5153-
everything is fine:
5129+
If we organize our crate differently by using modules, we'll need to ensure both
5130+
of the conditions are satisfied. Don't worry, you can lean on the compiler since
5131+
it won't let you get away with violating them.
51545132
51555133
```{rust}
5156-
use shapes::HasArea;
5134+
use shapes::HasArea; // satisfies #1
51575135
51585136
mod shapes {
51595137
use std::f64::consts;
@@ -5175,8 +5153,8 @@ mod shapes {
51755153
}
51765154
}
51775155
5178-
51795156
fn main() {
5157+
// use shapes::HasArea; // This would satisfy #1, too
51805158
let c = shapes::Circle {
51815159
x: 0.0f64,
51825160
y: 0.0f64,
@@ -5187,18 +5165,25 @@ fn main() {
51875165
}
51885166
```
51895167
5190-
This means that even if someone does something bad like add methods to `int`,
5191-
it won't affect you, unless you `use` that trait.
5168+
Requiring us to `use` traits whose methods we want means that even if someone
5169+
does something bad like add methods to `int`, it won't affect us, unless you
5170+
`use` that trait.
5171+
5172+
The second condition allows us to `impl` built-in `trait`s for types we define,
5173+
or allows us to `impl` our own `trait`s for built-in types, but restricts us
5174+
from mixing and matching third party or built-in `impl`s with third party or
5175+
built-in types.
51925176
5193-
There's one more restriction on implementing traits. Either the trait or the
5194-
type you're writing the `impl` for must be inside your crate. So, we could
5195-
implement the `HasArea` type for `int`, because `HasArea` is in our crate. But
5196-
if we tried to implement `Float`, a trait provided by Rust, for `int`, we could
5197-
not, because both the trait and the type aren't in our crate.
5177+
We could `impl` the `HasArea` trait for `int`, because `HasArea` is in our
5178+
crate. But if we tried to implement `Float`, a standard library `trait`, for
5179+
`int`, we could not, because neither the `trait` nor the `type` are in our
5180+
crate.
51985181
5199-
One last thing about traits: generic functions with a trait bound use
5200-
**monomorphization** ("mono": one, "morph": form), so they are statically
5201-
dispatched. What's that mean? Well, let's take a look at `print_area` again:
5182+
## Monomorphization
5183+
5184+
One last thing about generics and traits: the compiler performs
5185+
**monomorphization** on generic functions so they are statically dispatched. To
5186+
see what that means, let's take a look at `print_area` again:
52025187
52035188
```{rust,ignore}
52045189
fn print_area<T: HasArea>(shape: T) {
@@ -5215,10 +5200,11 @@ fn main() {
52155200
}
52165201
```
52175202
5218-
When we use this trait with `Circle` and `Square`, Rust ends up generating
5219-
two different functions with the concrete type, and replacing the call sites with
5220-
calls to the concrete implementations. In other words, you get something like
5221-
this:
5203+
Because we have called `print_area` with two different types in place of its
5204+
type paramater `T`, Rust will generate two versions of the function with the
5205+
appropriate concrete types, replacing the call sites with calls to the concrete
5206+
implementations. In other words, the compiler will actually compile something
5207+
more like this:
52225208
52235209
```{rust,ignore}
52245210
fn __print_area_circle(shape: Circle) {
@@ -5239,10 +5225,12 @@ fn main() {
52395225
}
52405226
```
52415227
5242-
The names don't actually change to this, it's just for illustration. But
5243-
as you can see, there's no overhead of deciding which version to call here,
5244-
hence 'statically dispatched'. The downside is that we have two copies of
5245-
the same function, so our binary is a little bit larger.
5228+
These names are for illustration; the compiler will generate its own cryptic
5229+
names for internal uses. The point is that there is no runtime overhead of
5230+
deciding which version to call. The function to be called is determined
5231+
statically, at compile time. Thus, generic functions are **statically
5232+
dispatched**. The downside is that we have two similar functions, so our binary
5233+
is larger.
52465234
52475235
# Threads
52485236

0 commit comments

Comments
 (0)