A trait defines the functionality a particular type has and can share with other types. We can use traits to define shared behaviour in an abstract way (just like abstract class in java). We can use trait bounds to specify that a generic type can be any type that has certain behaviou.
Note: Traits are similar to a feature often called interfaces in other languages (eg, typescript), although with some differences.
pub trait Summary {
fn summarize(&self) -> String;
}
struct User {
name: String,
age: u32,
}
impl Summary for User { // for the user struct we have implemented the summary trait
fn summarize(&self) -> String {
return format!("User {} is {} years old" , self.name, self.age);
}
}
fn main () {
let user = User {
name: String::from("Tushar"),
age: 21,
};
println!("{}", user.summarize()); // whenever we define user, we can call summarize function
}Once a type implements a trait, you can call the trait method on instances of that type.
pub trait Summary {
fn summarize(&self) -> String { // default implementation
return String::from("Summarize");
}
}
struct User {
name: String,
age: u32,
}
impl Summary for User{}
fn main () {
let user = User {
name: String::from("Tushar"),
age: 21,
};
println!("{}", user.summarize()); // whenever we define user, we can call summarize function
}what does it do?
- whoever is implementing
Summary, in our case, itsUser, have their own implementation then that will be used - if they dont have their own implen=mentation then the default implementation will be used
We can use traits as function parameters to accept any type that implements that trait.
pub trait Summary {
fn summarize(&self) -> String;
}
struct User {
name: String,
age: u32,
}
impl Summary for User {
fn summarize(&self) -> String {
return format!("User {} is {} years old" , self.name, self.age);
}
}
pub fn notify(item: &impl Summary) { // this function takes sometjing as an input which implements "Summary"
println!("Breaking News! {}", item.summarize());
}
fn main() {
let user = User {
name: String::from("Tushar"),
age: 21,
};
notify(&user);
}&impl TraitName in parameters is syntactic sugar for trait bounds.
This is helpful when we don't need to specify the exact type, just the behavior it supports.
We can bind generic types to traits using trait bounds. This means the generic type must implement a specific trait.
pub fn notify<T: Summary>(item: T) {
// Takes a generic T which must implement the Summary trait
println!("Breaking News! {}", item.summarize());
}In the above code:
Tis a generic type parameterT: Summaryis the trait bound, ensuringTimplements theSummarytrait
We can bind multiple traits using the + syntax:
pub fn notify<T: Summary + Fix>(item: &T) {
println!("Breaking News! {}", item.summarize());
}This means T must implement both Summary and Fix traits.
We can also use where clauses for cleaner syntax if the trait bounds get too long.
pub fn notify<T>(item: &T)
where
T: Summary + Fix,
{
println!("Breaking News! {}", item.summarize());
}