]> git.lizzy.rs Git - rust.git/blob - src/doc/trpl/traits.md
rollup merge of #21151: brson/beta
[rust.git] / src / doc / trpl / traits.md
1 % Traits
2
3 Do you remember the `impl` keyword, used to call a function with method
4 syntax?
5
6 ```{rust}
7 struct Circle {
8     x: f64,
9     y: f64,
10     radius: f64,
11 }
12
13 impl Circle {
14     fn area(&self) -> f64 {
15         std::f64::consts::PI * (self.radius * self.radius)
16     }
17 }
18 ```
19
20 Traits are similar, except that we define a trait with just the method
21 signature, then implement the trait for that struct. Like this:
22
23 ```{rust}
24 struct Circle {
25     x: f64,
26     y: f64,
27     radius: f64,
28 }
29
30 trait HasArea {
31     fn area(&self) -> f64;
32 }
33
34 impl HasArea for Circle {
35     fn area(&self) -> f64 {
36         std::f64::consts::PI * (self.radius * self.radius)
37     }
38 }
39 ```
40
41 As you can see, the `trait` block looks very similar to the `impl` block,
42 but we don't define a body, just a type signature. When we `impl` a trait,
43 we use `impl Trait for Item`, rather than just `impl Item`.
44
45 So what's the big deal? Remember the error we were getting with our generic
46 `inverse` function?
47
48 ```text
49 error: binary operation `==` cannot be applied to type `T`
50 ```
51
52 We can use traits to constrain our generics. Consider this function, which
53 does not compile, and gives us a similar error:
54
55 ```{rust,ignore}
56 fn print_area<T>(shape: T) {
57     println!("This shape has an area of {}", shape.area());
58 }
59 ```
60
61 Rust complains:
62
63 ```text
64 error: type `T` does not implement any method in scope named `area`
65 ```
66
67 Because `T` can be any type, we can't be sure that it implements the `area`
68 method. But we can add a *trait constraint* to our generic `T`, ensuring
69 that it does:
70
71 ```{rust}
72 # trait HasArea {
73 #     fn area(&self) -> f64;
74 # }
75 fn print_area<T: HasArea>(shape: T) {
76     println!("This shape has an area of {}", shape.area());
77 }
78 ```
79
80 The syntax `<T: HasArea>` means `any type that implements the HasArea trait`.
81 Because traits define function type signatures, we can be sure that any type
82 which implements `HasArea` will have an `.area()` method.
83
84 Here's an extended example of how this works:
85
86 ```{rust}
87 trait HasArea {
88     fn area(&self) -> f64;
89 }
90
91 struct Circle {
92     x: f64,
93     y: f64,
94     radius: f64,
95 }
96
97 impl HasArea for Circle {
98     fn area(&self) -> f64 {
99         std::f64::consts::PI * (self.radius * self.radius)
100     }
101 }
102
103 struct Square {
104     x: f64,
105     y: f64,
106     side: f64,
107 }
108
109 impl HasArea for Square {
110     fn area(&self) -> f64 {
111         self.side * self.side
112     }
113 }
114
115 fn print_area<T: HasArea>(shape: T) {
116     println!("This shape has an area of {}", shape.area());
117 }
118
119 fn main() {
120     let c = Circle {
121         x: 0.0f64,
122         y: 0.0f64,
123         radius: 1.0f64,
124     };
125
126     let s = Square {
127         x: 0.0f64,
128         y: 0.0f64,
129         side: 1.0f64,
130     };
131
132     print_area(c);
133     print_area(s);
134 }
135 ```
136
137 This program outputs:
138
139 ```text
140 This shape has an area of 3.141593
141 This shape has an area of 1
142 ```
143
144 As you can see, `print_area` is now generic, but also ensures that we
145 have passed in the correct types. If we pass in an incorrect type:
146
147 ```{rust,ignore}
148 print_area(5i);
149 ```
150
151 We get a compile-time error:
152
153 ```text
154 error: failed to find an implementation of trait main::HasArea for int
155 ```
156
157 So far, we've only added trait implementations to structs, but you can
158 implement a trait for any type. So technically, we _could_ implement
159 `HasArea` for `int`:
160
161 ```{rust}
162 trait HasArea {
163     fn area(&self) -> f64;
164 }
165
166 impl HasArea for int {
167     fn area(&self) -> f64 {
168         println!("this is silly");
169
170         *self as f64
171     }
172 }
173
174 5i.area();
175 ```
176
177 It is considered poor style to implement methods on such primitive types, even
178 though it is possible.
179
180 This may seem like the Wild West, but there are two other restrictions around
181 implementing traits that prevent this from getting out of hand. First, traits
182 must be `use`d in any scope where you wish to use the trait's method. So for
183 example, this does not work:
184
185 ```{rust,ignore}
186 mod shapes {
187     use std::f64::consts;
188
189     trait HasArea {
190         fn area(&self) -> f64;
191     }
192
193     struct Circle {
194         x: f64,
195         y: f64,
196         radius: f64,
197     }
198
199     impl HasArea for Circle {
200         fn area(&self) -> f64 {
201             consts::PI * (self.radius * self.radius)
202         }
203     }
204 }
205
206 fn main() {
207     let c = shapes::Circle {
208         x: 0.0f64,
209         y: 0.0f64,
210         radius: 1.0f64,
211     };
212
213     println!("{}", c.area());
214 }
215 ```
216
217 Now that we've moved the structs and traits into their own module, we get an
218 error:
219
220 ```text
221 error: type `shapes::Circle` does not implement any method in scope named `area`
222 ```
223
224 If we add a `use` line right above `main` and make the right things public,
225 everything is fine:
226
227 ```{rust}
228 use shapes::HasArea;
229
230 mod shapes {
231     use std::f64::consts;
232
233     pub trait HasArea {
234         fn area(&self) -> f64;
235     }
236
237     pub struct Circle {
238         pub x: f64,
239         pub y: f64,
240         pub radius: f64,
241     }
242
243     impl HasArea for Circle {
244         fn area(&self) -> f64 {
245             consts::PI * (self.radius * self.radius)
246         }
247     }
248 }
249
250
251 fn main() {
252     let c = shapes::Circle {
253         x: 0.0f64,
254         y: 0.0f64,
255         radius: 1.0f64,
256     };
257
258     println!("{}", c.area());
259 }
260 ```
261
262 This means that even if someone does something bad like add methods to `int`,
263 it won't affect you, unless you `use` that trait.
264
265 There's one more restriction on implementing traits. Either the trait or the
266 type you're writing the `impl` for must be inside your crate. So, we could
267 implement the `HasArea` type for `int`, because `HasArea` is in our crate.  But
268 if we tried to implement `Float`, a trait provided by Rust, for `int`, we could
269 not, because both the trait and the type aren't in our crate.
270
271 One last thing about traits: generic functions with a trait bound use
272 *monomorphization* (*mono*: one, *morph*: form), so they are statically
273 dispatched. What's that mean? Well, let's take a look at `print_area` again:
274
275 ```{rust,ignore}
276 fn print_area<T: HasArea>(shape: T) {
277     println!("This shape has an area of {}", shape.area());
278 }
279
280 fn main() {
281     let c = Circle { ... };
282
283     let s = Square { ... };
284
285     print_area(c);
286     print_area(s);
287 }
288 ```
289
290 When we use this trait with `Circle` and `Square`, Rust ends up generating
291 two different functions with the concrete type, and replacing the call sites with
292 calls to the concrete implementations. In other words, you get something like
293 this:
294
295 ```{rust,ignore}
296 fn __print_area_circle(shape: Circle) {
297     println!("This shape has an area of {}", shape.area());
298 }
299
300 fn __print_area_square(shape: Square) {
301     println!("This shape has an area of {}", shape.area());
302 }
303
304 fn main() {
305     let c = Circle { ... };
306
307     let s = Square { ... };
308
309     __print_area_circle(c);
310     __print_area_square(s);
311 }
312 ```
313
314 The names don't actually change to this, it's just for illustration. But
315 as you can see, there's no overhead of deciding which version to call here,
316 hence *statically dispatched*. The downside is that we have two copies of
317 the same function, so our binary is a little bit larger.
318
319 ## Our `inverse` Example
320
321 Back in [Generics](generics.html), we were trying to write code like this:
322
323 ```{rust,ignore}
324 fn inverse<T>(x: T) -> Result<T, String> {
325     if x == 0.0 { return Err("x cannot be zero!".to_string()); }
326
327     Ok(1.0 / x)
328 }
329 ```
330
331 If we try to compile it, we get this error:
332
333 ```text
334 error: binary operation `==` cannot be applied to type `T`
335 ```
336
337 This is because `T` is too generic: we don't know if a random `T` can be
338 compared. For that, we can use trait bounds. It doesn't quite work, but try
339 this:
340
341 ```{rust,ignore}
342 fn inverse<T: PartialEq>(x: T) -> Result<T, String> {
343     if x == 0.0 { return Err("x cannot be zero!".to_string()); }
344
345     Ok(1.0 / x)
346 }
347 ```
348
349 You should get this error:
350
351 ```text
352 error: mismatched types:
353  expected `T`,
354     found `_`
355 (expected type parameter,
356     found floating-point variable)
357 ```
358
359 So this won't work. While our `T` is `PartialEq`, we expected to have another `T`,
360 but instead, we found a floating-point variable. We need a different bound. `Float`
361 to the rescue:
362
363 ```
364 use std::num::Float;
365
366 fn inverse<T: Float>(x: T) -> Result<T, String> {
367     if x == Float::zero() { return Err("x cannot be zero!".to_string()) }
368
369     let one: T = Float::one();
370     Ok(one / x)
371 }
372 ```
373
374 We've had to replace our generic `0.0` and `1.0` with the appropriate methods
375 from the `Float` trait. Both `f32` and `f64` implement `Float`, so our function
376 works just fine:
377
378 ```
379 # use std::num::Float;
380 # fn inverse<T: Float>(x: T) -> Result<T, String> {
381 #     if x == Float::zero() { return Err("x cannot be zero!".to_string()) }
382 #     let one: T = Float::one();
383 #     Ok(one / x)
384 # }
385 println!("the inverse of {} is {:?}", 2.0f32, inverse(2.0f32));
386 println!("the inverse of {} is {:?}", 2.0f64, inverse(2.0f64));
387
388 println!("the inverse of {} is {:?}", 0.0f32, inverse(0.0f32));
389 println!("the inverse of {} is {:?}", 0.0f64, inverse(0.0f64));
390 ```