Introduction
Traits are functionality that a type shares with other types. We use traits to define such behavior in an abstract way.
Example
Let's say we have two structs of Pokemon and Digimon. Now both Pokemon and Digimon have a special move that we want to define as a trait. Hence we can write:
pub trait SpecialMove {
fn sp_move (&self) -> String;
}
Now we can implement SpecialMove differently for our Pokemon and Digimon. Something we've done below:
struct Pokemon {
pub id: u32,
pub name: String,
pub special_move: String,
pub types: Vec<String>
}
struct Digimon {
pub name: String,
pub special_move: String
}
impl SpecialMove for Pokemon {
fn sp_move (&self) -> String {
format!("id: {}, name: {}, sp_move: {}", self.id, self.name, self.special_move)
}
}
impl SpecialMove for Digimon {
fn sp_move (&self) -> String {
format!("name: {}, sp_move: {}", self.name, self.special_move)
}
}
So the sp_move fn for Pokemon outputes id, name, and special_move, on the other hand it outputs only name, and special_move for Digimon, because Digimon doesn't have an id field (it may have in the show, but not in this example)
So although it is a trait that carries some similarity, SpecialMove behaves different for Pokemon and Digimon, like it does in the show.
Now let's write the main fn.
fn main() {
let charizard = Pokemon {
id: 6,
name: String::from("Charizard"),
special_move: String::from("Flamethrower"),
types: vec![String::from("Fire"), String::from("Flying")],
};
let black_war_greymon = Digimon {
name: String::from("Black War Greymon"),
special_move: String::from("Terra Destroyer"),
};
println!("{}", charizard.sp_move());
println!("{}", black_war_greymon.sp_move());
}
Output:
id: 6, name: Charizard, sp_move: Flamethrower
name: Black War Greymon, sp_move: Terra Destroyer
Default Implementations
Now traits can also have some default implementations, which exists for all the structs. Let's change the SpecialMove trait accordingly.
pub trait SpecialMove {
fn sp_move (&self) -> String;
fn summarize (&self) -> String {
String::from("Every mon has a special move.")
}
}
So now every implementation of SpecialMove, be it a Pokemon, Digimon or Super Saiyan, it will contain the summarize method.
So let's change the main fn accordingly to test this behavior.
fn main() {
let charizard = Pokemon {
id: 6,
name: String::from("Charizard"),
special_move: String::from("Flamethrower"),
types: vec![String::from("Fire"), String::from("Flying")],
};
let black_war_greymon = Digimon {
name: String::from("Black War Greymon"),
special_move: String::from("Terra Destroyer"),
};
println!("{}", charizard.summarize());
println!("{}", charizard.sp_move());
println!("{}", charizard.summarize());
println!("{}", black_war_greymon.sp_move());
}
Output:
Every mon has a special move.
id: 6, name: Charizard, sp_move: Flamethrower
Every mon has a special move.
name: Black War Greymon, sp_move: Terra Destroyer
Passing Traits As Parameters
We can also pass traits as Parameters.
pub fn notify(mon: &impl SpecialMove) {
println!("Notification: {}", mon.summarize());
}
The notify fn will take a mon as an input that implements the trait SpecialMove, and it'll printout a notification by calling the summarize method, which is a default implementation of the trait.
So let's invoke notify in main:
fn main() {
// ...prev code
println!("{}", charizard.summarize());
println!("{}", charizard.sp_move());
println!("{}", charizard.summarize());
println!("{}", black_war_greymon.sp_move());
notify(&charizard);
}
Output:
Every mon has a special move.
id: 6, name: Charizard, sp_move: Flamethrower
Every mon has a special move.
name: Black War Greymon, sp_move: Terra Destroyer
Notification: Every mon has a special move.
Conclusion
We'll discuss little bit more about traits in the next article, specifically something called trait bounds, something that we've also briefly touched in the article about Generics. That's all for now, and cheers!