[go: up one dir, main page]

Impls & Traits

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