Skip to content

Commit 3a3b3b4

Browse files
committed
TRPL edits: traits
Let's talk about generics first, since we use traits to bound them in funtions. Partially addresses #24325
1 parent 8366df3 commit 3a3b3b4

File tree

2 files changed

+58
-186
lines changed

2 files changed

+58
-186
lines changed

src/doc/trpl/SUMMARY.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,10 +34,10 @@
3434
* [Method Syntax](method-syntax.md)
3535
* [Vectors](vectors.md)
3636
* [Strings](strings.md)
37+
* [Generics](generics.md)
3738
* [Traits](traits.md)
3839
* [Operators and Overloading](operators-and-overloading.md)
3940
* [Drop](drop.md)
40-
* [Generics](generics.md)
4141
* [if let](if-let.md)
4242
* [Trait Objects](trait-objects.md)
4343
* [Closures](closures.md)

src/doc/trpl/traits.md

Lines changed: 57 additions & 185 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,9 @@
11
% Traits
22

3-
Do you remember the `impl` keyword, used to call a function with method
4-
syntax?
3+
Do you remember the `impl` keyword, used to call a function with [method
4+
syntax][methodsyntax]?
55

6-
```{rust}
7-
# #![feature(core)]
6+
```rust
87
struct Circle {
98
x: f64,
109
y: f64,
@@ -18,11 +17,12 @@ impl Circle {
1817
}
1918
```
2019

20+
[methodsyntax]: method-syntax.html
21+
2122
Traits are similar, except that we define a trait with just the method
2223
signature, then implement the trait for that struct. Like this:
2324

24-
```{rust}
25-
# #![feature(core)]
25+
```rust
2626
struct Circle {
2727
x: f64,
2828
y: f64,
@@ -41,20 +41,13 @@ impl HasArea for Circle {
4141
```
4242

4343
As you can see, the `trait` block looks very similar to the `impl` block,
44-
but we don't define a body, just a type signature. When we `impl` a trait,
44+
but we dont define a body, just a type signature. When we `impl` a trait,
4545
we use `impl Trait for Item`, rather than just `impl Item`.
4646

47-
So what's the big deal? Remember the error we were getting with our generic
48-
`inverse` function?
49-
50-
```text
51-
error: binary operation `==` cannot be applied to type `T`
52-
```
53-
5447
We can use traits to constrain our generics. Consider this function, which
5548
does not compile, and gives us a similar error:
5649

57-
```{rust,ignore}
50+
```rust,ignore
5851
fn print_area<T>(shape: T) {
5952
println!("This shape has an area of {}", shape.area());
6053
}
@@ -66,11 +59,11 @@ Rust complains:
6659
error: type `T` does not implement any method in scope named `area`
6760
```
6861

69-
Because `T` can be any type, we can't be sure that it implements the `area`
70-
method. But we can add a *trait constraint* to our generic `T`, ensuring
62+
Because `T` can be any type, we cant be sure that it implements the `area`
63+
method. But we can add a trait constraint to our generic `T`, ensuring
7164
that it does:
7265

73-
```{rust}
66+
```rust
7467
# trait HasArea {
7568
# fn area(&self) -> f64;
7669
# }
@@ -83,10 +76,9 @@ The syntax `<T: HasArea>` means `any type that implements the HasArea trait`.
8376
Because traits define function type signatures, we can be sure that any type
8477
which implements `HasArea` will have an `.area()` method.
8578

86-
Here's an extended example of how this works:
79+
Heres an extended example of how this works:
8780

88-
```{rust}
89-
# #![feature(core)]
81+
```rust
9082
trait HasArea {
9183
fn area(&self) -> f64;
9284
}
@@ -144,10 +136,10 @@ This shape has an area of 3.141593
144136
This shape has an area of 1
145137
```
146138

147-
As you can see, `print_area` is now generic, but also ensures that we
148-
have passed in the correct types. If we pass in an incorrect type:
139+
As you can see, `print_area` is now generic, but also ensures that we have
140+
passed in the correct types. If we pass in an incorrect type:
149141

150-
```{rust,ignore}
142+
```rust,ignore
151143
print_area(5);
152144
```
153145

@@ -157,11 +149,11 @@ We get a compile-time error:
157149
error: failed to find an implementation of trait main::HasArea for int
158150
```
159151

160-
So far, we've only added trait implementations to structs, but you can
161-
implement a trait for any type. So technically, we _could_ implement
162-
`HasArea` for `i32`:
152+
So far, weve only added trait implementations to structs, but you can
153+
implement a trait for any type. So technically, we _could_ implement `HasArea`
154+
for `i32`:
163155

164-
```{rust}
156+
```rust
165157
trait HasArea {
166158
fn area(&self) -> f64;
167159
}
@@ -181,100 +173,55 @@ It is considered poor style to implement methods on such primitive types, even
181173
though it is possible.
182174

183175
This may seem like the Wild West, but there are two other restrictions around
184-
implementing traits that prevent this from getting out of hand. First, traits
185-
must be `use`d in any scope where you wish to use the trait's method. So for
186-
example, this does not work:
176+
implementing traits that prevent this from getting out of hand. The first is
177+
that if the trait isn’t defined in your scope, it doesn’t apply. Here’s an
178+
example: the standard library provides a [`Write`][write] trait which adds
179+
extra functionality to `File`s, for doing file I/O. By default, a `File`
180+
won’t have its methods:
187181

188-
```{rust,ignore}
189-
mod shapes {
190-
use std::f64::consts;
182+
[write]: ../std/io/trait.Write.html
191183

192-
trait HasArea {
193-
fn area(&self) -> f64;
194-
}
195-
196-
struct Circle {
197-
x: f64,
198-
y: f64,
199-
radius: f64,
200-
}
201-
202-
impl HasArea for Circle {
203-
fn area(&self) -> f64 {
204-
consts::PI * (self.radius * self.radius)
205-
}
206-
}
207-
}
208-
209-
fn main() {
210-
let c = shapes::Circle {
211-
x: 0.0f64,
212-
y: 0.0f64,
213-
radius: 1.0f64,
214-
};
215-
216-
println!("{}", c.area());
217-
}
184+
```rust,ignore
185+
let mut f = std::fs::File::open("foo.txt").ok().expect("Couldn’t open foo.txt");
186+
let result = f.write("whatever".as_bytes());
187+
# result.unwrap(); // ignore the erorr
218188
```
219189

220-
Now that we've moved the structs and traits into their own module, we get an
221-
error:
190+
Here’s the error:
222191

223192
```text
224-
error: type `shapes::Circle` does not implement any method in scope named `area`
225-
```
226-
227-
If we add a `use` line right above `main` and make the right things public,
228-
everything is fine:
229-
230-
```{rust}
231-
# #![feature(core)]
232-
mod shapes {
233-
use std::f64::consts;
234-
235-
pub trait HasArea {
236-
fn area(&self) -> f64;
237-
}
238-
239-
pub struct Circle {
240-
pub x: f64,
241-
pub y: f64,
242-
pub radius: f64,
243-
}
193+
error: type `std::fs::File` does not implement any method in scope named `write`
244194
245-
impl HasArea for Circle {
246-
fn area(&self) -> f64 {
247-
consts::PI * (self.radius * self.radius)
248-
}
249-
}
250-
}
195+
let result = f.write(b”whatever”);
196+
^~~~~~~~~~~~~~~~~~
197+
```
251198

252-
use shapes::HasArea;
199+
We need to `use` the `Write` trait first:
253200

254-
fn main() {
255-
let c = shapes::Circle {
256-
x: 0.0f64,
257-
y: 0.0f64,
258-
radius: 1.0f64,
259-
};
201+
```rust,ignore
202+
use std::io::Write;
260203
261-
println!("{}", c.area());
262-
}
204+
let mut f = std::fs::File::open("foo.txt").ok().expect("Couldn’t open foo.txt");
205+
let result = f.write("whatever".as_bytes());
206+
# result.unwrap(); // ignore the erorr
263207
```
264208

209+
This will compile without error.
210+
265211
This means that even if someone does something bad like add methods to `int`,
266-
it won't affect you, unless you `use` that trait.
212+
it wont affect you, unless you `use` that trait.
267213

268-
There's one more restriction on implementing traits. Either the trait or the
269-
type you're writing the `impl` for must be inside your crate. So, we could
270-
implement the `HasArea` type for `i32`, because `HasArea` is in our crate. But
214+
Theres one more restriction on implementing traits. Either the trait or the
215+
type youre writing the `impl` for must be defined by you. So, we could
216+
implement the `HasArea` type for `i32`, because `HasArea` is in our code. But
271217
if we tried to implement `Float`, a trait provided by Rust, for `i32`, we could
272-
not, because both the trait and the type aren't in our crate.
218+
not, because neither the trait nor the type are in our code.
273219

274220
One last thing about traits: generic functions with a trait bound use
275-
*monomorphization* (*mono*: one, *morph*: form), so they are statically
276-
dispatched. What's that mean? Check out the chapter on [trait
277-
objects](trait-objects.html) for more.
221+
‘monomorphization’ (mono: one, morph: form), so they are statically dispatched.
222+
What’s that mean? Check out the chapter on [trait objects][to] for more details.
223+
224+
[to]: trait-objects.html
278225

279226
## Multiple trait bounds
280227

@@ -302,7 +249,7 @@ fn foo<T: Clone + Debug>(x: T) {
302249
## Where clause
303250

304251
Writing functions with only a few generic types and a small number of trait
305-
bounds isn't too bad, but as the number increases, the syntax gets increasingly
252+
bounds isnt too bad, but as the number increases, the syntax gets increasingly
306253
awkward:
307254

308255
```
@@ -318,7 +265,7 @@ fn foo<T: Clone, K: Clone + Debug>(x: T, y: K) {
318265
The name of the function is on the far left, and the parameter list is on the
319266
far right. The bounds are getting in the way.
320267

321-
Rust has a solution, and it's called a '`where` clause':
268+
Rust has a solution, and its called a `where` clause:
322269

323270
```
324271
use std::fmt::Debug;
@@ -389,84 +336,9 @@ This shows off the additional feature of `where` clauses: they allow bounds
389336
where the left-hand side is an arbitrary type (`i32` in this case), not just a
390337
plain type parameter (like `T`).
391338

392-
## Our `inverse` Example
393-
394-
Back in [Generics](generics.html), we were trying to write code like this:
395-
396-
```{rust,ignore}
397-
fn inverse<T>(x: T) -> Result<T, String> {
398-
if x == 0.0 { return Err("x cannot be zero!".to_string()); }
399-
400-
Ok(1.0 / x)
401-
}
402-
```
403-
404-
If we try to compile it, we get this error:
405-
406-
```text
407-
error: binary operation `==` cannot be applied to type `T`
408-
```
409-
410-
This is because `T` is too generic: we don't know if a random `T` can be
411-
compared. For that, we can use trait bounds. It doesn't quite work, but try
412-
this:
413-
414-
```{rust,ignore}
415-
fn inverse<T: PartialEq>(x: T) -> Result<T, String> {
416-
if x == 0.0 { return Err("x cannot be zero!".to_string()); }
417-
418-
Ok(1.0 / x)
419-
}
420-
```
421-
422-
You should get this error:
423-
424-
```text
425-
error: mismatched types:
426-
expected `T`,
427-
found `_`
428-
(expected type parameter,
429-
found floating-point variable)
430-
```
431-
432-
So this won't work. While our `T` is `PartialEq`, we expected to have another `T`,
433-
but instead, we found a floating-point variable. We need a different bound. `Float`
434-
to the rescue:
435-
436-
```
437-
# #![feature(std_misc)]
438-
use std::num::Float;
439-
440-
fn inverse<T: Float>(x: T) -> Result<T, String> {
441-
if x == Float::zero() { return Err("x cannot be zero!".to_string()) }
442-
443-
let one: T = Float::one();
444-
Ok(one / x)
445-
}
446-
```
447-
448-
We've had to replace our generic `0.0` and `1.0` with the appropriate methods
449-
from the `Float` trait. Both `f32` and `f64` implement `Float`, so our function
450-
works just fine:
451-
452-
```
453-
# #![feature(std_misc)]
454-
# use std::num::Float;
455-
# fn inverse<T: Float>(x: T) -> Result<T, String> {
456-
# if x == Float::zero() { return Err("x cannot be zero!".to_string()) }
457-
# let one: T = Float::one();
458-
# Ok(one / x)
459-
# }
460-
println!("the inverse of {} is {:?}", 2.0f32, inverse(2.0f32));
461-
println!("the inverse of {} is {:?}", 2.0f64, inverse(2.0f64));
462-
463-
println!("the inverse of {} is {:?}", 0.0f32, inverse(0.0f32));
464-
println!("the inverse of {} is {:?}", 0.0f64, inverse(0.0f64));
465-
```
466-
467339
## Default methods
468340

469-
There's one last feature of traits we should cover: default methods. It's
341+
Theres one last feature of traits we should cover: default methods. Its
470342
easiest just to show an example:
471343

472344
```rust
@@ -477,8 +349,8 @@ trait Foo {
477349
}
478350
```
479351

480-
Implementors of the `Foo` trait need to implement `bar()`, but they don't
481-
need to implement `baz()`. They'll get this default behavior. They can
352+
Implementors of the `Foo` trait need to implement `bar()`, but they dont
353+
need to implement `baz()`. Theyll get this default behavior. They can
482354
override the default if they so choose:
483355

484356
```rust

0 commit comments

Comments
 (0)