The authoritative guide to Rust in 5 minutes (18)

The authoritative guide to Rust in 5 minutes (18)

trait

Traits have similar functions to interfaces in TS, but they are not exactly the same. For example, attributes and methods can be defined in interfaces, but only methods can be defined in traits.

Define trait

Use the trait keyword followed by the trait name, and the curly brackets identify the method that the type with the trait should implement:

trait Summary { //A trait can contain multiple methods: each method signature occupies a separate line and ends with a semicolon. fn summarize (& self ) -> String ; } Copy code

Implement traits for types

It can be understood as the implements keyword in TS. In rust, impl is used followed by the trait name, followed by the for keyword and the type name. The following is to implement the Summary trait for NewsArticle and Tweet types:

struct NewsArticles { headlline: String , content: String , author: String , location: String } //Implement Summary impl Summary for NewsArticles for NewsArticles { fn summarize (& self ) -> String { //self represents the specific type of the currently called method, which is very similar to this in TS format! ( "{}-{}-{ }-{}" , self .headlline, self .author, self .location, self .content) } } let news = NewsArticles { headlline: String ::from( "headline" ), content: String ::from( "content" ), author: String ::from( "author" ), location: String ::from( "location" ) }; //Call summary() method println! ( "{}" , news.summarize()); //headline-author-location-content struct Tweet { username: String , content: String , reply: String , retweet: String , } //Implement Summary impl Summary for Tweet { fn summarize (& self ) -> String { format! ( "{}-{}-{}-{}" , self .username, self .content, self .reply, self .retweet) } } let tweet = Tweet { username: String ::from( "username" ), content: String ::from( "content" ), reply: String ::from( "reply" ), retweet: String ::from( "retweet" ), }; //call the Summarize () method of the println! ( "{}" , Tweet.summarize ()) //username-Content-Reply-to retweet duplicated code

Implement custom traits for external structures:

//Implement custom traits for the standard library impl Summary for String { fn summarize (& self ) -> String { "impl Summary for String" .to_string() } } //Call to summarize string implementation () method println! ( "{}" , String :: from ( "abc" ) .summarize ()) //impl the Summary for String Copy the code

Implement the trait of the external library for the structure we defined:

use std::fmt; //After implementing the Display trait in the standard library, you can print the type on the console. //No need to pay attention to the code details, just understand that we can implement the trait in the standard library for our custom type. impl fmt::Display for Tweet { fn fmt (& self , f: & mut fmt::Formatter< '_ >) -> fmt:: Result { write! (f, "({}, {}, {}, {})" , self .username, self .content, self .reply, self .retweet) } } //Display the realization of the normal print tweet, without the need to add {:?} Enable Debug mode println! ( "{}" , Tweet); //(username, Content, Reply, retweet) Copy the code

Implement external traits for external structures:

//Here is the implementation of the Display trait in the standard library for the Vec type in the standard library. impl <T> std::fmt::Display for std::vec:: Vec <T> { //An error is reported, only when the trait or one of them When a type is defined in our library, //can we implement the corresponding trait for that type. } Copy code

The above error is due to the existence of orphan rules in rust: the reason is so named because its parent type is not defined in the current library. This rule is also part of program coherence, which ensures that what other people write will not break your code, and vice versa. Without this rule, two libraries can implement the same trait for the same type, and Rust will not be able to determine which version should be used.

Default implementation

The method signature of traits can be implemented by default, reducing the repetitive code writing when multiple types of methods are the same:

trait User { fn user (& self ) -> String { String ::from( "unknow" ) } } //Implement User trait for NewsArticles impl User for NewsArticles { //Since user has a default implementation, there is no need to implement it here } the println! ( "{}" , news.user ()) //unknow duplicated code

You can also call other methods in the same trait in the default implementation, even if these methods are not implemented by default:

trait User { fn user (& self ) -> & String ; fn info (& self ) -> & String { //user() has no default implementation, but it can be called, because when the structure implements the User trait, it will definitely be Implement user() method self the .user () } } impl User for NewsArticles { //The user() method will be implemented here fn user (& self ) -> & String { & self .author } } println! ( "{}" , news.info()) //author copy the code

Use traits to constrain parameters

Using the impl keyword, the parameters in the function must implement a certain trait:

fn notify (item: impl Summary) {//Require item to implement Summary println! ( "{}" , item.summarize()) } Notify (News) //headline author-LOCATION-Content- duplicated code

The above is actually just a kind of syntactic sugar, the complete "trait constraint" is implemented using generics:

//Follow the required traits after the generic parameters fn notify <T: Summary>(item: T) { println! ( "{}" , item.summarize()) } Copy code

The impl Trait syntactic sugar is more suitable for short examples, while the trait constraint is more suitable for complex situations. For example, when there are multiple parameters, the trait constraint is more concise:

Trait impl// Fn Notify (Item: impl the Summary, ITEM2: impl the Summary) {} //trait bound Fn Notify <T: the Summary> (Item: T, ITEM2: T) {} copy the code

Allow multiple traits to restrict parameters:

//Use the plus sign to require item to implement both the Summary and Display traits fn notify <T: Summary + std::fmt::Display>(item: T) { println! ( "{}" , item.summarize( )) } Copy code

Use the where syntax to simplify trait restrictions:

fn some_function <T: Summary + std::fmt::Display, U: Debug + Clone >(param1: T, param2: U) {} //equivalent to: fn some_function <T, U>(param1: T, param2: U) where T: Summary + std::fmt::Display, U: Summary + Clone { //... } Copy code

Limit the return value type

Using traits, you can also require that the return value implements a trait:

//The return value is required to implement the Summary trait fn notify (item: String ) -> impl Summary { Tweet { username: item, content: String ::from( "content" ), reply: String ::from( "reply" ), retweet: String ::from( "retweet" ), } } notify( "str" .to_string()); Copy code

In special cases, Tweet and NewArticles both implement Summary but fail to compile:

fn notify (switch: bool ) -> impl Summary { if switch { NewsArticles { headlline: String ::from( "headline" ), content: String ::from( "content" ), author: String ::from( "author" ), location: String ::from( "location" ), } } else { //Error, if and else types are not compatible, this is caused by the way traits work //Later, I will talk about how to use "trait objects" to store different types of values Tweet { username: String ::from( "username" ), content: String ::from( "content" ), reply: String ::from( "reply" ), retweet: String ::from( "retweet" ), } } } notify( true ); Copy code

Use traits to solve the largest function problem in the generic chapter

Remember the largest function in the previous chapter? At that time, I ignored the problem of error reporting. Here we use trait to solve it. Let me recall this function first:

//Find the largest value in the array fn largest <T>(list: &[T]) -> T { let mut largest = list[ 0 ]; for &item in list.iter() { if item> largest { //An error will be reported here, because T may be any type, not all types can be compared largest = item } } largest } println! ( "{}" , largest(&[ 'a' , 'b' , 'c' , 'd' ])) println! ( "{}" , largest(&[ 1 , 2 , 3 , 3 ] )) Copy the code

Use trait constraints to solve:

//The T type is required to implement the PartialOrd and Copy traits//PartialOrd is used to implement the comparison function, //Copy is used to copy the value when taking the value, not the transfer of ownership fn largest <T: std::cmp:: PartialOrd + Copy >(list: &[T]) -> T { let mut largest = list[ 0 ]; //Copy is needed here, because for example, when the parameters are all String types, the ownership of members cannot be removed for &item in list. iter() { if item> largest { //std::cmp::PartialOrd is needed here, because not all data support comparison largest = item } } largest } println! ( "{}" , largest(&[ 'a' , 'b' , 'c' , 'd' ])); //d println! ( "{}" , largest(&[ 1 , 2 , 3 , 3 ])) // 3Copy code

Use traits to conditionally implement methods

When implementing a method for a structure, it can be implemented conditionally. For example, when the structure meets the trait restriction conditions, a method can be implemented for the structure:

#[derive(Debug)] struct Pair <T> { x: T, y: T, } //Implement a method with a generic type T impl <T> Pair<T> { fn new (x: T, y: T) -> Self { Self { x,y } } } let pair = Pair::new( 100 , 50 ); println! ( "{:?}" , pair); //Pair {x: 100, y: 50} //Implement a method that requires structure member types //Here, the T type is required to implement the Display and PartialOrd traits //Only the type Pair<T> that implements these two traits can//implement the cmp_display method impl <T: std::fmt::Display + std::cmp:: PartialOrd > Pair<T> { fn cmp_display (& self ) -> bool { if self .x> self .y { println! ( "larger:{}" , self .x) } else { println! ( "smaller:{}" , self .y) } } } pair.cmp_display (); //larger: 100 copy the code

Coverage implementation (blanketimplementation)

For all types of implementations that satisfy trait constraints, this feature really bursts:

//Define a Log trait trait Log { fn log (& self ); } //Note that for T here directly implements the log method for all types that implement Display. impl <T: std::fmt::Display> Log for T { fn log (& self ) { println! ( "{}" , self ) } } 2 .log (); //2 news.log () //error, the top news unrealized Display duplicated code

It can be implemented for all types without trait restrictions:

trait LogAlwaysOne { fn log_one (& self ) { println! ( "{}" , 1 ) } } //Implement LogAlwaysOne trait impl <T> LogAlwaysOne for T {} for all types //For example, the following types can call log_one() method 1 .log_one(); //1 '1' .log_one(); //1 "1" .log_one(); //1 news.log_one(); // 1Copy code