π‘ 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!
}