mirror of
https://github.com/darkicewolf50/RustBrock.git
synced 2025-06-15 13:04:18 -06:00
finished ch10.2
This commit is contained in:
parent
4c18f833b0
commit
05ef5ce477
@ -0,0 +1 @@
|
||||
I need to organize this later... and spell check it... also later
|
211
Traits.md
211
Traits.md
@ -192,3 +192,214 @@ This would print `1 new tweet: (Read more from @horse_ebooks...)`
|
||||
Note: it is impossible to call the default implementation from an overridden implementation of that same method
|
||||
|
||||
## Traits as Parameters
|
||||
This can be used to only accept types that have implemented the trait
|
||||
|
||||
Here is an example
|
||||
This is a function calls the `summarize` method on its `item` parameter which is some type that implenets the `Summary` trait
|
||||
|
||||
To do this we need to use the `impl Trait` syntax
|
||||
```rust
|
||||
pub fn notify(item: &impl Summary) {
|
||||
println!("Breaking news! {}", item.summarize());
|
||||
}
|
||||
```
|
||||
Instaed of a create tpye ofr the `item` paremeter, use instead specify the `impl` keyword and the trait name
|
||||
|
||||
This allows for the parameter to accept any type that implemetns the specified trait
|
||||
|
||||
In the body of the function we can call any method on item that come from the `Summary` trait, like `summarize`
|
||||
|
||||
We cant call any type that doesn't implement the `Summary` trait like `String` or `i32`, this wont compile either and give an error stating that it needs to implement the methods from the `Summary` trait
|
||||
|
||||
### Trait Bound Syntax
|
||||
The `impl Trait` syntax works for straitforward cases but is actually syntax sugar for a longer form known as a *trait bound*
|
||||
|
||||
It looks like this
|
||||
```rust
|
||||
pub fn notify<T: Summary>(item: &T) {
|
||||
println!("Breaking news! {}", item.summarize());
|
||||
}
|
||||
```
|
||||
|
||||
This is longer but it is equivalent to the example before, but way more verbose and boilerplate
|
||||
|
||||
We place trait bounds with the declaration of the generic type parameter after a colon and inside angle brackets
|
||||
|
||||
The `impl Trait` syntax is convenient and makes for more concise code in simple cases, while the fuller trait bound syntax can express more complexity in other cases
|
||||
|
||||
For example we can have two parameters that implement `Summary`
|
||||
Here is the `impl Trait` syntax
|
||||
```rust
|
||||
pub fn notify(item1: &impl Summary, item2: &impl Summary) {
|
||||
```
|
||||
|
||||
Here is the same thing with the trait bound syntax
|
||||
```rust
|
||||
pub fn notify<T: Summary>(item1: &T, item2: &T) {
|
||||
```
|
||||
|
||||
The `impl Trait` is appropriate if we want the function to allow `item1` and `item2` to have different types `<X, Y>` (for example)
|
||||
|
||||
If you want to force the two parameters to have the same type then you have to use the trait bound syntax
|
||||
|
||||
### Specifying Multiple Trait Bounds with the + Syntax
|
||||
You can specifiy more than one trait bound, lets say you need methods from two traits, here is an example that uses the `Summary` trait and the `Display trait`
|
||||
|
||||
You use the `+` after the previous one to specify any additional traits
|
||||
|
||||
In `impl Trait` syntax
|
||||
```rust
|
||||
pub fn notify(item: &(impl Summary + Display)) {
|
||||
```
|
||||
In trait bound syntax
|
||||
```rust
|
||||
pub fn notify<T: Summary + Display>(item: &T) {
|
||||
```
|
||||
With the two trait bounds specified, the body of `notify` can call `summarize` and use `{}` to format item.
|
||||
|
||||
### Clearer Trait Bounds with where Clauses
|
||||
Using too many trait bounds has its downside of unclear code, this is multipled by each generic needs its own trait bound, so types with multiple parameters can contain lots of trait bound info between the functions name and its paratmeter list
|
||||
|
||||
This can make the signautre hard to read
|
||||
|
||||
There is an alternative in rust using the `where` keyword clause after the function signature
|
||||
|
||||
Here is the orignal bad code
|
||||
```rust
|
||||
fn some_function<T: Display + Clone, U: Clone + Debug>(t: &T, u: &U) -> i32 {
|
||||
```
|
||||
|
||||
Here is the equivalent with the `where` clause
|
||||
```rust
|
||||
fn some_function<T, U>(t: &T, u: &U) -> i32
|
||||
where
|
||||
T: Display + Clone,
|
||||
U: Clone + Debug,
|
||||
{
|
||||
```
|
||||
|
||||
This fucntion's signaure is less cluttered, the function name, parameter list, and return type are close toheter, similar to a function with lots of trait bounds
|
||||
|
||||
## Returning Types That Implements Traits
|
||||
You can also use the `impl Trait` syntax in the retrun a value of some type that implements a trait
|
||||
|
||||
Here is an example of this
|
||||
```rust
|
||||
fn returns_summarizable() -> impl Summary {
|
||||
Tweet {
|
||||
username: String::from("horse_ebooks"),
|
||||
content: String::from(
|
||||
"of course, as you probably already know, people",
|
||||
),
|
||||
reply: false,
|
||||
retweet: false,
|
||||
}
|
||||
}
|
||||
```
|
||||
This specifies that the function returns some tpye that implements the `Summary` trait without naming the concete type
|
||||
|
||||
The code that calls this function doesnt need to know the concrete type it returns, like in this case
|
||||
|
||||
This is useful to specify a return type by the type that it implements, especially in the context of closures and iterators
|
||||
|
||||
Closures and iterators create tpyes that only the complier knows or tpyes that are very long to specify
|
||||
|
||||
The `impl Trait` syntax lets you specify that a function returns som type that implements the `Iterator` trait without needs to write out a very long type.
|
||||
|
||||
You can only use `impl Trait` if you are returning a single type
|
||||
|
||||
For example this code that returns either a `NewsArticle` or a `Tweet` with the return tpye specified as `impl Summary` wouldn't work
|
||||
```rust
|
||||
fn returns_summarizable(switch: bool) -> impl Summary {
|
||||
if switch {
|
||||
NewsArticle {
|
||||
headline: String::from(
|
||||
"Penguins win the Stanley Cup Championship!",
|
||||
),
|
||||
location: String::from("Pittsburgh, PA, USA"),
|
||||
author: String::from("Iceburgh"),
|
||||
content: String::from(
|
||||
"The Pittsburgh Penguins once again are the best \
|
||||
hockey team in the NHL.",
|
||||
),
|
||||
}
|
||||
} else {
|
||||
Tweet {
|
||||
username: String::from("horse_ebooks"),
|
||||
content: String::from(
|
||||
"of course, as you probably already know, people",
|
||||
),
|
||||
reply: false,
|
||||
retweet: false,
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Returning either a `NewsArticle` or a `Tweet` isnt allowed because the compiler has restrictions on how `impl Trait` syntax is implelented
|
||||
|
||||
This will be covered in a later section
|
||||
|
||||
## Using Trait Bounds to Conditionally Implement Methods
|
||||
By sing a trait bound with an `impl` block that uses generic tpye parameters, we can implement methods conditionally for tpyes that implement the specified traits
|
||||
|
||||
For example the type `Pair<T>` always implements the `new` function to reutnr a new instance of `Pair<T>`
|
||||
|
||||
But the net `impl` block `Pair<T>` only implements the `cmp_display` method if its inner tpye `T` implements the `PartialOrd` trait that enalbes comparision *and* the `Display` trait that enables printing
|
||||
|
||||
```rust
|
||||
use std::fmt::Display;
|
||||
|
||||
struct Pair<T> {
|
||||
x: T,
|
||||
y: T,
|
||||
}
|
||||
|
||||
impl<T> Pair<T> {
|
||||
fn new(x: T, y: T) -> Self {
|
||||
Self { x, y }
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Display + PartialOrd> Pair<T> {
|
||||
fn cmp_display(&self) {
|
||||
if self.x >= self.y {
|
||||
println!("The largest member is x = {}", self.x);
|
||||
} else {
|
||||
println!("The largest member is y = {}", self.y);
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
We cna also conditionally implement a trait for an type that implements another trait.
|
||||
|
||||
Implementations of a trait on an type that satifies the trait bounds are called *blanket implementations*
|
||||
|
||||
The are used extensively in the Rust std library
|
||||
|
||||
An example of this is the `ToString` trait on any tpye that implements the `Display` trait
|
||||
|
||||
The `impl` block in the std library look similar to this code
|
||||
```rust
|
||||
impl<T: Display> ToString for T {
|
||||
// --snip--
|
||||
}
|
||||
```
|
||||
we can call the `to_string` method defined by the `ToString` trait on any tpye that implements the `Display` trait because of the blanket implementation in the std library
|
||||
|
||||
Here is an example, we can turn integers into their corresponding `String` values like this because integers implement `Display` trait
|
||||
```rust
|
||||
let s = 3.to_string();
|
||||
```
|
||||
|
||||
Traits and trait bounds let us write code that use generic type parameters to reduce duplication but also specifiy to the compiler that we want a genric tpye with particular behavior.
|
||||
|
||||
The compiler can then ue this into to check the concrete types used in your code to provide the correct behavior.
|
||||
|
||||
In dynamically typed languages we would get a runtime error if we called a method on a type that didnt define the method.
|
||||
|
||||
Rust provides these erros at compile time so that you are forced to fix these problems could even possibily happen.
|
||||
|
||||
This also makes it so that we have to write code that check for the behavior becuase the compiler already check for us.
|
||||
|
||||
This improves performance without having to give up the flexibility of generics.
|
Loading…
x
Reference in New Issue
Block a user