Impls

  • Earlier, we discussed that structs and enums group related data, while impl blocks and traits add associated and shared behavior to the data.
  • Usage of Self vs self keywords:
    • Self: Refers to the type itself (the blueprint).
    • self: Refers to the instance of the type (the actual data).
      • ๐Ÿ’ฏ This can be any form of self, &self, &mut self, self: Box<Self>, self: Pin<&mut Self>, etc.
  • There are multiple ways to implement a behavior for a type. We discuss only about the impl blocks with this article. The patterns involving traits are discussed under Traits.

Inherent impls

Implement associated functions, methods, and constants directly for a type.

โญ๏ธ The implementation must be in the same crate as the type.

impl Type

struct Person {
    name: String,
}

impl Person {
    const GREET: &str = "Hello!";

    fn greet(&self) -> String { // &self` is shorthand for self: &Self
        format!("{} I am {}.", Self::GREET, self.name) // ๐Ÿ’กSelf to access type; self to access instance
    }
}

fn main() {
    let steve = Person { name: "Steve".to_string() };
    println!("{}", steve.greet()) // Hello! I am Steve.
}
  • Inside the impl block,
    • Constants belong to the type itself.
      • To access them, use the Self keyword or the type name.
      • Example: Self::GREET (preferred in Rust) or Person::GREET.
    • Methods (functions that access instance fields) must send the instance/ self as the first parameter.
      • This can be self, &self, &mut self, self: Box<Self>, self: Pin<&mut Self>, etc.) as the first parameter.

impl<T> Type<T>

struct Point<T> {
    x: T,
    y: T,
}

impl<T> Point<T> {
    fn into_tuple(self) -> (T, T) {
        (self.x, self.y)
    }
}

fn main() {
    let a = Point { x: 0, y: 1 }; // a: Point<i32>
    let b = a.into_tuple(); // (0, 1)
    println!("{b:?}")
}

// ๐Ÿ’ก into_tuple() take self (not &self) and consumes the self / instance.
// ๐Ÿ’ก we can pass &self (as a reference) and use as_ prefix to borrow the instance.
// fn as_tuple(&self) -> (&T, &T) { (&self.x, &self.y) }

Associated Functions & Methods

  • Associated functions:
    • Functions that are associated with a particular data type via the impl block.
    • Can call from the type with :: operator.
      • Person::new(), Vec::new(), String::from()
  • Methods:
    • Associated functions with a receiver of self, &self, &mut self, self: Box<Self>, self: Pin<&mut Self>, etc.
    • Can call from the instance with . operator or from the type (as methods are also associated functions) with :: operator, but we need to pass the instance as the first parameter always.
      • steve.greet() or Person::greet(&steve)
      • "hello".to_string() or String::to_string("hello")
struct Person {
    name: String,
    company_name: String,
}

impl Person {
    // ๐Ÿ’ก The constructor (new` is a conventional name, not a keyword)
    fn new(name: String, company_name: String) -> Self { // an associated function and not a method
        Self { name, company_name }
    }

    fn intro_name(&self) -> String { // a method
        format!("I'm {}", self.name)
    }

    fn intro_company(&self) -> String { // a method
        format!("I'm from {}", self.company_name)
    }
}

fn main() {
    // Call from the type with `::` operator
    let steve = Person::new(String::from("Steve Jobs"), String::from("Apple"));

    // Call from the instance with `.` operator
    println!("{}. {}.", steve.intro_name(), steve.intro_company()); // I'm Steve Jobs. I'm from Apple.

    // As methods are also associated functions; Call from the type with `::` operator and pass the instance as the first parameter
    println!("{}. {}.", Person::intro_name(&steve), Person::intro_company(&steve)); // I'm Steve Jobs. I'm from Apple.
}

We can use the type/ Person instead Self keyword in the new function. By the way, using the Self keyword is considered idiomatic in Rust.

fn new(name: String, company_name: String) -> Person {
    Person { name, company_name }
}

๐Ÿ‘จโ€๐Ÿซ Before going to the next…

  • Familiarize with the conventional prefixes and suffixes used in methods names.
    PrefixPostfixself takenself type
    as_none&self or &mut selfany
    from_nonenoneany
    into_noneselfany
    is_none&mut self or &self or noneany
    to__mut&mut selfany
    to_not _mutselfCopy
    to_not _mut&selfnot Copy