💡 When we discussed about C-like structs, I mentioned that those are similar to classes in OOP languages but without their methods. impls are used to define methods for Rust structs and enums.
💡 Traits are kind of similar to interfaces in OOP languages. They are used to define the functionality a type must provide. Multiple traits can be implemented for a single type.
⭐️️ But traits can also include default implementations of methods. Default methods can be overridden when implementing types.
Impls without traits
struct Player {
first_name: String,
last_name: String,
}
impl Player {
fn full_name(&self) -> String {
format!("{} {}", self.first_name, self.last_name)
}
}
fn main() {
let player_1 = Player {
first_name: "Rafael".to_string(),
last_name: "Nadal".to_string(),
};
println!("Player 01: {}", player_1.full_name());
}
// ⭐️ Implementation must appear in the same crate as the self type
Impls & traits, without default methods
struct Player {
first_name: String,
last_name: String,
}
trait FullName {
fn full_name(&self) -> String;
}
impl FullName for Player {
fn full_name(&self) -> String {
format!("{} {}", self.first_name, self.last_name)
}
}
fn main() {
let player_2 = Player {
first_name: "Roger".to_string(),
last_name: "Federer".to_string(),
};
println!("Player 02: {}", player_2.full_name());
}
// 🔎 Other than functions, traits can contain constants and types.
// 💡 And also in Rust, new traits can be implemented for existing types even for types like i8, f64 and etc.
// Same way existing traits can be implemented for new types you are creating.
Impls, traits & default methods
trait Foo {
fn bar(&self);
fn baz(&self) { println!("We called baz."); }
}
⭐️ As you can see methods take a special first parameter, the type itself. It can be either self, &self, or &mut self; self if it’s a value on the stack (taking ownership), &self if it’s a reference, and &mut self if it’s a mutable reference.
Impls with Associated functions
Some other languages support static methods. At such times, we call a function directly through the class without creating an object. In Rust, we call them Associated Functions. we use :: instead of . when calling them from the struct.
ex. Person::new(“Elon Musk Jr”);
struct Player {
first_name: String,
last_name: String,
}
impl Player {
fn new(first_name: String, last_name: String) -> Player {
Player {
first_name: first_name,
last_name: last_name,
}
}
fn full_name(&self) -> String {
format!("{} {}", self.first_name, self.last_name)
}
}
fn main() {
let player_name = Player::new("Serena".to_string(), "Williams".to_string()).full_name();
println!("Player: {}", player_name);
}
// We have used :: notation for `new()` and . notation for `full_name()`
// 🔎 Also in here, instead of using new() and full_name() separately as two expressions,
// we can use Method Chaining. ex. `player.add_points(2).get_point_count();`
Traits with generics
trait From<T> {
fn from(T) -> Self;
}
impl From<u8> for u16 {
//...
}
impl From<u8> for u32{
//...
}
// Should specify after the trait name like generic functions
Traits inheritance
trait Person {
fn full_name(&self) -> String;
}
trait Employee : Person { // Employee inherits from person trait
fn job_title(&self) -> String;
}
trait ExpatEmployee : Employee + Expat { // ExpatEmployee inherits from Employee and Expat traits
fn additional_tax(&self) -> f64;
}
Trait objects
🔎 While Rust favors static dispatch, it also supports dynamic dispatch through a mechanism called ‘trait objects.’
🅆 Dynamic dispatch is the process of selecting which implementation of a polymorphic operation (method or function) to call at run time.
trait GetSound {
fn get_sound(&self) -> String;
}
struct Cat {
sound: String,
}
impl GetSound for Cat {
fn get_sound(&self) -> String {
self.sound.clone()
}
}
struct Bell {
sound: String,
}
impl GetSound for Bell {
fn get_sound(&self) -> String {
self.sound.clone()
}
}
fn make_sound<T: GetSound>(t: &T) {
println!("{}!", t.get_sound())
}
fn main() {
let kitty = Cat { sound: "Meow".to_string() };
let the_bell = Bell { sound: "Ding Dong".to_string() };
make_sound(&kitty); // Meow!
make_sound(&the_bell); // Ding Dong!
}