DEV Community

Oludayo Adeoye
Oludayo Adeoye

Posted on

🦀 Rust Master Class - Chapter 12: Deep Dive into Traits

🦀 Rust Master Class - Chapter 12: Deep Dive into Traits


Struggling with Estonian cases, a stranger helped me. In return, I helped her with Rust traits. We didn't speak the same language, but traits are the great equalizer — they only care what you can do.


Traits in Rust define a set of methods that a type must provide to support shared behavior across different structures . They are a fundamental tool for polymorphism, allowing functions to operate on any type that implements a specific trait .

1. Basic Definition and Implementation

A trait defines a blueprint for methods. When a struct implements a trait, it must provide a concrete implementation for those methods .

Code Example:

trait GeneralInfo {
    fn info(&self) -> (&str, u8, char);
}

struct Student {
    name_std: String,
    age: u8,
    sex: char,
}

impl GeneralInfo for Student {
    fn info(&self) -> (&str, u8, char) {
        (&self.name_std, self.age, self.sex)
    }
}
Enter fullscreen mode Exit fullscreen mode

2. Static vs. Dynamic Dispatch

Rust provides two ways to use traits:

  • Static Dispatch: Uses trait bounds (e.g., <T: Print>). The compiler generates specific code for each concrete type at compile time, leading to better performance .
  • Dynamic Dispatch: Uses trait objects (e.g., &dyn Print). The specific method to call is determined at runtime using a vtable. This allows for more flexibility, such as storing different types in a single vector, provided they all implement the same trait .

Code Example:

// Static Dispatch
fn static_display<T: Print>(value: T) { value.print(); }

// Dynamic Dispatch
fn display_dynamic(value: Vec<Box<dyn Print>>) {
    for i in value { i.print(); }
}
Enter fullscreen mode Exit fullscreen mode

3. Associated Types

Associated types allow a trait to define a placeholder type that is specified during implementation . This is often preferred over generics when a trait's implementation for a specific type will only ever use one concrete type .

Code Example:

trait DistanceThreeHours {
    type Distance; // Placeholder type
    fn distance_in_three_hours(&self) -> Self::Distance;
}

impl DistanceThreeHours for Kmh {
    type Distance = Km;
    fn distance_in_three_hours(&self) -> Self::Distance {
        Km { value: self.value * 3 }
    }
}
Enter fullscreen mode Exit fullscreen mode

4. Super Traits and Marker Traits

  • Super Traits: You can define a trait that requires another trait to be implemented first. For example, a Student trait might require the type to also implement the Person trait .
  • Marker Traits: These are traits with no methods, used only to provide information to the compiler about how a type can be used (e.g., Sized, Send, Sync) .

Code Example (Super Trait):

trait Person { fn name(&self) -> &str; }
trait Student: Person { // Student requires Person
    fn complete_info(&self) -> (&str, u8, &str);
}
Enter fullscreen mode Exit fullscreen mode

5. Operator Overloading

Rust allows you to implement standard operators (like +, -, *) for custom types by implementing specific traits from the std::ops module, such as Add, Sub, or Mul .

Code Example:

use std::ops::Mul;

impl Mul for Complex {
    type Output = Complex;
    fn mul(self, rhs: Complex) -> Self::Output {
        Complex {
            real: self.real * rhs.real - self.imag * rhs.imag,
            imag: self.real * rhs.imag + self.imag * rhs.real,
        }
    }
}
Enter fullscreen mode Exit fullscreen mode

6. Important Rules and Patterns

  • Orphan Rule: You can only implement a trait for a type if either the trait or the type is local to your crate .
  • Sealed Traits: A pattern used to prevent external crates from implementing a trait, ensuring that only the defining crate can provide implementations .
  • Trait Aliases: An unstable feature that allows you to combine multiple traits into a single name for convenience (e.g., trait PrintableAndCalculable = Printable + Calculable;) .
  • Trait Object Limitations: For a trait to be "object safe" (used as dyn Trait), its methods cannot have generic parameters or return Self, as the exact type is erased at runtime

📖 Download the full PDF: https://drive.google.com/file/d/1BtzPSU0XTeRJHnYO7JUS7EMvRwznfMLZ/view?usp=sharing

Part 12 of the Rust Master Class series — STEM EdTech | Automation Consulting | Rust Tutoring

RustLang #Programming #LearnToCode #STEM #EdTech

Top comments (0)