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

// 💡 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.
// But we can not implement existing traits into existing types.

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.

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