Trust learning — traits

Time:2021-2-24

Traits: defining common behaviors — Characteristics

Features tell the rust compiler what a particular type has and can be shared with other types. We can use features to define sharing behavior in an abstract way. We can use feature ranges to specify that generics can be any type with a specific behavior.

Note: features are similar to what other languages usually call interfaces, but with some differences.

Define a feature

The behavior of a type contains methods that we can call on that type. If we can call the same method on all of these types, different types have the same behavior. Feature definition is a method that groups method signatures together to define the behavior needed to achieve certain purposes.

For example, suppose we have multiple structures that can hold various types and quantities of text: the NewsArticle structure can hold news stories filed in specific locations, while a tweet can contain up to 280 characters and indicate whether it is a new tweet, forward a tweet, or reply to another tweet.

We want to create a media aggregator library to display data summaries that may be stored in NewsArticle or tweet instances. To do this, we need a summary of each type, and we need to request the summary by calling the summary method on the instance
/src/lib.rs

    pub trait Summary {
        fn summarize(&self) -> String;
    }

Here, we use the trait keyword to declare a feature, and then use the name of the feature, in this case summary. In braces, we declare method signatures that describe the behavior of the type that implements this feature, in this casefn summarize(&self) -> String

After the method signature, we use a semicolon instead of providing the implementation in braces. Each type that implements this feature must provide its own custom behavior for the method body. The compiler will force any type with a digest feature to use this signature to accurately define the method digest.

A feature can contain multiple methods in its body: method signature, one in each row and one in each row, and each line ends with a semicolon.

Implement features on types
Now that we have defined the required behavior using the summary feature, we can implement it on types in the media aggregator. The following example shows the implementation of the summary feature on the NewsArticle structure, which uses the title, author, and location to create the return value of the summary. For the tweet structure, assuming that the content of the tweet is limited to 280 characters, we define summary as the user name, and then the entire text of the tweet.
/src/main.rs

    pub trait Summary {
        fn summarize(&self) -> String;
    }

    pub struct Tweet {
        pub username: String,
        pub content: String,
        pub reply: bool,
        pub retweet: bool,
    }

    impl Summary for Tweet {
        fn summarize(&self) -> String {
            format!("{}: {}", self.username, self.content)
        }
    }

If a feature is used to define a method, its syntax is as follows:impl Traits for Object {
For example, let’s use the above example:

fn main() {
    let tweet = Tweet {
        User name: String:: from ("e-book"),
        content: String::from(
            "Of course, as you know, people.",
        ),
        reply: false,
        retweet: false,
    };

    Println! "A new message: {}", tweet.summarize ());
}

The running results are as follows

D:\learn\cargo_learn>cargo run
   Compiling cargo_learn v0.1.0 (D:\learn\cargo_learn)
    Finished dev [unoptimized + debuginfo] target(s) in 0.62s
     Running `target\debug\cargo_learn.exe`
New news: e-books: of course, as you know, people

Default implementation

Sometimes it’s useful to have default behavior for some or all methods in a feature, rather than requiring that all methods of each type be implemented. Then, when we implement features on a particular type, we can keep or override the default behavior of each method.

The following example shows how to specify the default string for the summary method of summary trail instead of defining only the method signature as in the previous example:

pub trait Summary {
    fn summarize(&self) -> String {
        String:: from ("default return value")
    }
}

pub struct NewsArticle {
    pub headline: String,
    pub location: String,
    pub author: String,
    pub content: String,
}

impl Summary for NewsArticle {
    fn summarize(&self) -> String {
        format!("{}, by {} ({})", self.headline, self.author, self.location)
    }
}

fn main() {
    let article = NewsArticle {
        Headline: String:: from ("a push message"),
        Location: String:: from ("address"),
        Author: String:: from ("author"),
        content: String::from(
            "Default content XXX\
                Title Content“
        ),
    };

    Println! "Push: {}", article.summarize ());

}
D:\learn\cargo_learn>cargo run
   Compiling cargo_learn v0.1.0 (D:\learn\cargo_learn)
    Finished dev [unoptimized + debuginfo] target(s) in 0.60s
     Running `target\debug\cargo_learn.exe`
Push: a push message, by author (address)

If theimpl Summary for NewsArticle {Content left blank:impl Summary for NewsArticle {}Then the running results are as follows:

D:\learn\cargo_learn>cargo run
   Compiling cargo_learn v0.1.0 (D:\learn\cargo_learn)
    Finished dev [unoptimized + debuginfo] target(s) in 0.61s
     Running `target\debug\cargo_learn.exe`
Push: default return value

The default implementation can call other methods with the same characteristics, even if other methods do not have a default implementation. In this way, features can provide many useful functions, and only a small part of them is required to be specified by the implementer. For example, we can define a summary feature to have a summary that needs to be implemented_ The author method, and then defines a summary method with a default implementation that calls summary_ The author method is as follows

pub trait Summary {
    fn summarize_author(&self) -> String;
    fn summarize(&self) -> String {
        Read more from {} )",  self.summarize_ author())
    }

}

pub struct Tweet {
    pub username: String,
    pub content: String,
    pub reply: bool,
    pub retweet: bool,
}

impl Summary for Tweet {
    fn summarize_author(&self) -> String {
        format!("@{}", self.username)
    }
}

fn main() {
    let tweet = Tweet {
        reply: false,
        retweet: false,
        User name: String:: from ("author Zuoz"),
        Content: String:: from ("default content XXX, default content"),
    };

    Println! "Push: {}", tweet.summarize ());

}

D:\learn\cargo_learn>cargo run
   Compiling cargo_learn v0.1.0 (D:\learn\cargo_learn)
    Finished dev [unoptimized + debuginfo] target(s) in 0.60s
     Running `target\debug\cargo_learn.exe`
Tweet: (read more from @ author Zuoz )

Characteristics as parameters
Features can also be used as parameters. In fact, part of the above example is extracted as a common method

fn main() {
    let tweet = Tweet {
        reply: false,
        retweet: false,
        User name: String:: from ("author Zuoz"),
        Content: String:: from ("default content XXX, default content"),
    };

    //Println! "Push: {}", tweet.summarize ());
    fn notify(item: &impl Summary) {
        Println! "Test: {}", item.summarize ());
    }

    notify(&tweet);
}

Results of operation:

D:\learn\cargo_learn>cargo run
   Compiling cargo_learn v0.1.0 (D:\learn\cargo_learn)
    Finished dev [unoptimized + debuginfo] target(s) in 0.61s
     Running `target\debug\cargo_learn.exe`
Test: (read more from @ author Zuoz )

Instead of the specific type of the item parameter, we specify the impl keyword and the feature name. This parameter accepts any type that implements the specified feature. In the notify body, we can call any method on the item from the summary attribute, such as summary. We can call NewsArticle or tweet in any case and deliver the notice. The code for this function cannot be called with any other type, such as string or I32, because these types are not implementedSummary

Feature constrained grammar
impl TraitGrammars are suitable for simple situations, but are actually grammars of longer forms (called feature binding). It looks like this:

pub fn notify<T: Summary>(item: &T) {
    Println! "Test: {}", item.summarize ());
}

This is the same result as the above example.

impl TraitSyntax is very convenient, and in simple cases can make the code more concise. In other cases, feature binding syntax can represent more complexity. For example, we can have two implementationsSummaryThe parameters of. useimpl TraitThe syntax is as follows:

pub fn notify(item1: &impl Summary, item2: &impl Summary) {

And obviously, Item1 and Item2 can make two different types, but both can call the summary method directly on them. If the two objects are of the same type, it will look like this:

pub fn notify<T: Summary>(item1: &T, item2: &T) {

The generic type T specified as the parameter types of Item1 and Item2 constrains the function so that the concrete types of values passed as parameters of Item1 and Item2 must be the same.

Use the + syntax to specify the use of multiple features
We can also specify multiple feature bindings. Suppose we’re going to inform theitemUsing the display format andsummarizeMethod: we specify in the notification definition that the project must be implemented at the same timeDisplayandSummary. We can do this with the + syntax:
pub fn notify(item: &(impl Summary + Display)) {

+Syntax is also valid for the feature range of generic types:
pub fn notify<T: Summary + Display>(item: &T) {

Complex implementation relationships can be simplified by using the where keyword, for example:

fn some_function<T: Display + Clone, U: Clone + Debug>(t: &T, u: &U) -> i32 {

It can be simplified as follows:

fn some_function<T, U>(t: T, u: U) -> i32
    where T: Display + Clone,
          U: Clone + Debug
{

The signature of the function is not too confusing: the function name, parameter list and return type are side by side, similar to a function without many feature boundaries.

Returns an instance of the type that implements the feature

We can also use impl trait syntax at the return location to return some type of value that implements the feature, as follows:

pub trait Summary {
    fn summarize(&self) -> String;
}

pub struct NewsArticle {
    pub headline: String,
    pub location: String,
    pub author: String,
    pub content: String,
}

impl Summary for NewsArticle {
    fn summarize(&self) -> String {
        format!("{}, by {} ({})", self.headline, self.author, self.location)
    }
}

pub struct Tweet {
    pub username: String,
    pub content: String,
    pub reply: bool,
    pub retweet: bool,
}

impl Summary for Tweet {
    fn summarize(&self) -> String {
        format!("{}: {}", self.username, self.content)
    }
}

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,
    }
}

By using impl summary for the return type, we specify return_ The Summarizable function returns a type that implements the summary feature without naming the specific type. In this case, turns_ Summarizable returns a tweet, but the code calling this function doesn’t know that.

However, impl trait can only be used when a single type is returned. For example, if the return type of NewsArticle or tweet returned by this code is specified as impl summary, it will not work properly:

pub trait Summary {
    fn summarize(&self) -> String;
}

pub struct NewsArticle {
    pub headline: String,
    pub location: String,
    pub author: String,
    pub content: String,
}

impl Summary for NewsArticle {
    fn summarize(&self) -> String {
        format!("{}, by {} ({})", self.headline, self.author, self.location)
    }
}

pub struct Tweet {
    pub username: String,
    pub content: String,
    pub reply: bool,
    pub retweet: bool,
}

impl Summary for Tweet {
    fn summarize(&self) -> String {
        format!("{}: {}", self.username, self.content)
    }
}

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,
        }
    }
}

Due to the limitation of implementation in compilerimpl TraitSyntax, so it is not allowed to return NewsArticle or tweet.

Fix Max problem:

An error was reported when using generics to get the maximum value before:

$ cargo run
error[E0369]: binary operation `>` cannot be applied to type `T`
 --> src/main.rs:5:17
  |
5 |         if item > largest {
  |            ---- ^ ------- T
  |            |
  |            T
  |
  = note: `T` might need a bound for `std::cmp::PartialOrd`

In the body of getting the maximum value, we want to use the greater than (>) operator to compare two values of type T. Because this operator is defined as a standard library featurestd::cmp::PartialOrdSo we need to specify partialord in the characteristic range of t so that the largest function can act on any type of slice that we can compare. We don’t need to include partialord in the study because it’s in the prelude. Change the largest signature as follows:

fn largest<T: PartialOrd>(list: &[T]) -> T {
    let mut largest = list[0];

    for &item in list {
        if item > largest {
            largest = item;
        }
    }

    largest
}

fn main() {
    let number_list = vec![34, 50, 25, 100, 65];

    let result = largest(&number_list);
    println!("The largest number is {}", result);

    let char_list = vec!['y', 'm', 'a', 'q'];

    let result = largest(&char_list);
    println!("The largest char is {}", result);
}

But at run time:

D:\learn\cargo_learn>cargo run
   Compiling cargo_learn v0.1.0 (D:\learn\cargo_learn)
error[E0508]: cannot move out of type `[T]`, a non-copy slice
 --> src\main.rs:2:23
  |
2 |     let mut largest = list[0];
  |                       ^^^^^^^
  |                       |
  |                       cannot move out of here
  |                       move occurs because `list[_]` has type `T`, which does not implement the `Copy` trait
  |                       help: consider borrowing here: `&list[0]`

error[E0507]: cannot move out of a shared reference
 --> src\main.rs:4:18
  |
4 |     for &item in list {
  |         -----    ^^^^
  |         ||
  |         |data moved here
  |         |move occurs because `item` has type `T`, which does not implement the `Copy` trait
  |         help: consider removing the `&`: `item`

error: aborting due to 2 previous errors

Some errors have detailed explanations: E0507, E0508.
For more information about an error, try `rustc --explain E0507`.
error: could not compile `cargo_learn`.

To learn more, run the command again with --verbose.

The key row in this error cannot be moved out of the non copy slice [t] type. For the non generic version of the largest function, we just try to find the largest I32 or char. It is well known that types of known size, such as I32 and char, can be stored on the stack, so they implement the copy feature. However, when we make the largest function universal, it is possible to implement the type that does not implement the copy feature in the list parameter. Therefore, we can’t move the value out of the list [0] and into the largest variable, resulting in this error.

To call this code only with those types that implement the copy feature, we can add copy to the feature range of T! The following example shows the complete code of a general maximum function. As long as the value type in slice passed to the function implements the partialord and copy features, the code can be compiled, such as I32 and char do

fn largest<T: PartialOrd + Copy>(list: &[T]) -> T {
    let mut largest = list[0];

    for &item in list {
        if item > largest {
            largest = item;
        }
    }

    largest
}

fn main() {
    let number_list = vec![34, 50, 25, 100, 65];

    let result = largest(&number_list);
    println!("The largest number is {}", result);

    let char_list = vec!['y', 'm', 'a', 'q'];

    let result = largest(&char_list);
    println!("The largest char is {}", result);
}

D:\learn\cargo_learn>cargo run
   Compiling cargo_learn v0.1.0 (D:\learn\cargo_learn)
    Finished dev [unoptimized + debuginfo] target(s) in 0.70s
     Running `target\debug\cargo_learn.exe`
The largest number is 100
The largest char is y

Using trait boundaries to implement methods conditionally
By using features bound to impl blocks that use generic type parameters, we can conditionally implement methods for the types that implement the specified features. For example, in the following examplePair<T>Types always implement new functionality. howeverPair<T>Only in its internal type T can the comparison be realizedPartialOrdFeatures and printableDisplayOnly when the feature is changedcmp_displaymethod.

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 larger number is: x = {}", self. X);
        } else {
            Println! ("the larger number is: y = {}", self;
        }
    }
}

For any type that implements another feature, we can also conditionally implement a feature. Any type of feature implementation that satisfies the feature boundary is called a package implementation and is widely used in rust standard library. For example, the standard library is implemented in theDisplayAny type of featureToStringfeatures. In the standard libraryimplThe block looks like the following code:

impl<T: Display> ToString for T {
    // --snip--
}

Because the standard library has this comprehensive implementation, we can implement it in the libraryDisplayCalled on any type of the featureToStringDefinition of featuresto_stringmethod. For example, we can convert an integer to its correspondingStringValue, because integers implementDisplay

impl<T: Display> ToString for T {
    // --snip--
}

Because the standard library has this overall implementation, we can call to defined by toString on any type that implements the display feature_ String method. For example, we can convert an integer to its corresponding string value like this, because the integer implements display:let s = 3.to_string();

Recommended Today

Yosemite 10.10.3 beta 4 download address OS x10.10.3 beta 4 Official Download

Yosemite 10.10.3beta 3 has just been released, and apple has officially launched OS x10.10.3beta 4. The following editor will bring you the official download address of OS x10.10.3beta 4. Let’s go to download and experience it with interested friends. Apple released the fourth beta of OS X Yosemite to developers and beta users today, just […]