Rust Traits
A Rust trait defines functionality of a set of types that implements the trait.
Define Traits
We can define new traits with the trait
keyword
pub trait Summary {
fn summarize(&self) -> String;
}
Implement Traits
Traits are implemented for types using the impl Trait for Type
syntax:
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)
}
}
And then we can just call trait methods like normal methods. E.g. article.summarize()
.
We can’t implement external traits for external types because of Rust traits’ orphan rule. Though we can use extension traits to achieve the same effect.
Default Implementations
We can also provides a default implementation for a trait:
pub trait Summary {
fn summarize(&self) -> String {
String::from("(Read more...)")
}
}
A default implementation can call other method in the same trait, even for those methods which does not have a default implementation.
Traits as Parameters
See: Argument-Position Impl Trait
We can also pass traits as a parameter to functions:
pub fn notify(item: &impl Summary) {
println!("Breaking news! {}", item.summarize());
}
The above syntax is a shorthand for the Trait bound syntax:
pub fn notify<T: Summary>(item: &T) {
println!("Breaking news! {}", item.summarize());
}
We can also combine multiple traits with +
:
pub fn notify(item: &(impl Summary + Display)) {
or
pub fn notify<T: Summary + Display>(item: &T) {
Where Syntax
where
syntax can make function signature clearer when there are a lot of trait bound.
Instead of writing something like
fn some_function<T: Display + Clone, U: Clone + Debug>(t: &T, u: &U) -> i32 {
we can write
fn some_function<T, U>(t: &T, u: &U) -> i32
where T: Display + Clone,
U: Clone + Debug
{
Returning Types that Implement Traits
See: Return-Position impl Trait
We can use impl trait
in return position to return a value of some type that implement a trait:
```rust
fn returns_summarizable() -> impl Summary {
Tweet { ... }
}
Use Trait Bound to Conditionally Implement Methods
Just like C++ concept, we can use trait bound to conditionally implement methods:
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);
}
}
}