3 Do you remember the `impl` keyword, used to call a function with method
14 fn area(&self) -> f64 {
15 std::f64::consts::PI * (self.radius * self.radius)
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:
31 fn area(&self) -> f64;
34 impl HasArea for Circle {
35 fn area(&self) -> f64 {
36 std::f64::consts::PI * (self.radius * self.radius)
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`.
45 So what's the big deal? Remember the error we were getting with our generic
49 error: binary operation `==` cannot be applied to type `T`
52 We can use traits to constrain our generics. Consider this function, which
53 does not compile, and gives us a similar error:
56 fn print_area<T>(shape: T) {
57 println!("This shape has an area of {}", shape.area());
64 error: type `T` does not implement any method in scope named `area`
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
73 # fn area(&self) -> f64;
75 fn print_area<T: HasArea>(shape: T) {
76 println!("This shape has an area of {}", shape.area());
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.
84 Here's an extended example of how this works:
88 fn area(&self) -> f64;
97 impl HasArea for Circle {
98 fn area(&self) -> f64 {
99 std::f64::consts::PI * (self.radius * self.radius)
109 impl HasArea for Square {
110 fn area(&self) -> f64 {
111 self.side * self.side
115 fn print_area<T: HasArea>(shape: T) {
116 println!("This shape has an area of {}", shape.area());
137 This program outputs:
140 This shape has an area of 3.141593
141 This shape has an area of 1
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:
151 We get a compile-time error:
154 error: failed to find an implementation of trait main::HasArea for int
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
163 fn area(&self) -> f64;
166 impl HasArea for int {
167 fn area(&self) -> f64 {
168 println!("this is silly");
177 It is considered poor style to implement methods on such primitive types, even
178 though it is possible.
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:
187 use std::f64::consts;
190 fn area(&self) -> f64;
199 impl HasArea for Circle {
200 fn area(&self) -> f64 {
201 consts::PI * (self.radius * self.radius)
207 let c = shapes::Circle {
213 println!("{}", c.area());
217 Now that we've moved the structs and traits into their own module, we get an
221 error: type `shapes::Circle` does not implement any method in scope named `area`
224 If we add a `use` line right above `main` and make the right things public,
231 use std::f64::consts;
234 fn area(&self) -> f64;
243 impl HasArea for Circle {
244 fn area(&self) -> f64 {
245 consts::PI * (self.radius * self.radius)
252 let c = shapes::Circle {
258 println!("{}", c.area());
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.
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.
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:
276 fn print_area<T: HasArea>(shape: T) {
277 println!("This shape has an area of {}", shape.area());
281 let c = Circle { ... };
283 let s = Square { ... };
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
296 fn __print_area_circle(shape: Circle) {
297 println!("This shape has an area of {}", shape.area());
300 fn __print_area_square(shape: Square) {
301 println!("This shape has an area of {}", shape.area());
305 let c = Circle { ... };
307 let s = Square { ... };
309 __print_area_circle(c);
310 __print_area_square(s);
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.
319 ## Our `inverse` Example
321 Back in [Generics](generics.html), we were trying to write code like this:
324 fn inverse<T>(x: T) -> Result<T, String> {
325 if x == 0.0 { return Err("x cannot be zero!".to_string()); }
331 If we try to compile it, we get this error:
334 error: binary operation `==` cannot be applied to type `T`
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
342 fn inverse<T: PartialEq>(x: T) -> Result<T, String> {
343 if x == 0.0 { return Err("x cannot be zero!".to_string()); }
349 You should get this error:
352 error: mismatched types:
355 (expected type parameter,
356 found floating-point variable)
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`
366 fn inverse<T: Float>(x: T) -> Result<T, String> {
367 if x == Float::zero() { return Err("x cannot be zero!".to_string()) }
369 let one: T = Float::one();
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
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();
385 println!("the inverse of {} is {:?}", 2.0f32, inverse(2.0f32));
386 println!("the inverse of {} is {:?}", 2.0f64, inverse(2.0f64));
388 println!("the inverse of {} is {:?}", 0.0f32, inverse(0.0f32));
389 println!("the inverse of {} is {:?}", 0.0f64, inverse(0.0f64));