Impls and 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 overriden 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

Impls, traits & default methods

⭐️ 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 struct. ex. Person::new(“Elon Musk Jr”);

Traits with generics

Traits inheritance

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.

Last updated