Chapter 3: Advanced Traits

Traits are Rust's superpower for abstraction. This chapter explores advanced trait patterns that enable powerful, flexible, and type-safe designs.

3.1 Trait Objects and Dynamic Dispatch

While generics use static dispatch (monomorphization), trait objects enable dynamic dispatch when you need runtime polymorphism:

Creating Trait Objects

trait Drawable {
    fn draw(&self);
}

struct Circle {
    radius: f64,
}

struct Rectangle {
    width: f64,
    height: f64,
}

impl Drawable for Circle {
    fn draw(&self) {
        println!("Drawing circle with radius {}", self.radius);
    }
}

impl Drawable for Rectangle {
    fn draw(&self) {
        println!("Drawing rectangle {}x{}", self.width, self.height);
    }
}

// Static dispatch: monomorphized at compile time
fn draw_static<T: Drawable>(item: &T) {
    item.draw();
}

// Dynamic dispatch: runtime polymorphism
fn draw_dynamic(item: &dyn Drawable) {
    item.draw();
}

fn main() {
    let shapes: Vec<Box<dyn Drawable>> = vec![
        Box::new(Circle { radius: 5.0 }),
        Box::new(Rectangle { width: 10.0, height: 20.0 }),
    ];

    for shape in shapes {
        shape.draw();
    }
}
Static vs Dynamic Dispatch

Object Safety Rules

Not all traits can be made into trait objects. A trait is object-safe if:

// Object-safe trait
trait Display {
    fn display(&self);
}

// NOT object-safe: has generic method
trait NotObjectSafe {
    fn generic<T>(&self, value: T);  // โŒ
}

// NOT object-safe: returns Self
trait AlsoNotObjectSafe {
    fn clone_self(&self) -> Self;  // โŒ
}

// Object-safe version
trait ObjectSafeClone {
    fn clone_box(&self) -> Box<dyn ObjectSafeClone>;
}

impl<T> ObjectSafeClone for T
where
    T: Clone + 'static,
{
    fn clone_box(&self) -> Box<dyn ObjectSafeClone> {
        Box::new(self.clone())
    }
}

3.2 Advanced Trait Bounds

Multiple Bounds and Where Clauses

use std::fmt::{Debug, Display};

// Multiple bounds inline
fn print_info<T: Debug + Display + Clone>(item: T) {
    println!("Debug: {:?}", item);
    println!("Display: {}", item);
    let copy = item.clone();
}

// Better: use where clause for readability
fn better_print_info<T>(item: T)
where
    T: Debug + Display + Clone,
{
    println!("Debug: {:?}", item);
    println!("Display: {}", item);
}

// Complex bounds with associated types
fn process<T, U>(iter: T)
where
    T: Iterator<Item = U>,
    U: Display + Clone,
{
    for item in iter {
        println!("{}", item);
    }
}

Conditional Trait Implementation

use std::fmt;

struct Pair<T> {
    first: T,
    second: T,
}

// Always implement this
impl<T> Pair<T> {
    fn new(first: T, second: T) -> Self {
        Pair { first, second }
    }
}

// Only implement for types that support comparison
impl<T: PartialOrd> Pair<T> {
    fn largest(&self) -> &T {
        if self.first >= self.second {
            &self.first
        } else {
            &self.second
        }
    }
}

// Blanket implementation: implement for all T that implement Display
impl<T: fmt::Display> fmt::Display for Pair<T> {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        write!(f, "({}, {})", self.first, self.second)
    }
}

3.3 Operator Overloading

Rust allows operator overloading through trait implementation:

use std::ops::{Add, Mul, Neg};

#[derive(Debug, Clone, Copy)]
struct Vector2D {
    x: f64,
    y: f64,
}

impl Add for Vector2D {
    type Output = Self;

    fn add(self, other: Self) -> Self {
        Vector2D {
            x: self.x + other.x,
            y: self.y + other.y,
        }
    }
}

impl Mul<f64> for Vector2D {
    type Output = Self;

    fn mul(self, scalar: f64) -> Self {
        Vector2D {
            x: self.x * scalar,
            y: self.y * scalar,
        }
    }
}

impl Neg for Vector2D {
    type Output = Self;

    fn neg(self) -> Self {
        Vector2D {
            x: -self.x,
            y: -self.y,
        }
    }
}

fn main() {
    let v1 = Vector2D { x: 1.0, y: 2.0 };
    let v2 = Vector2D { x: 3.0, y: 4.0 };

    let v3 = v1 + v2;           // Add
    let v4 = v1 * 2.0;          // Mul
    let v5 = -v1;               // Neg

    println!("{:?}", v3);
}

Index and IndexMut

use std::ops::{Index, IndexMut};

struct Grid<T> {
    data: Vec<T>,
    width: usize,
    height: usize,
}

impl<T> Index<(usize, usize)> for Grid<T> {
    type Output = T;

    fn index(&self, (x, y): (usize, usize)) -> &T {
        &self.data[y * self.width + x]
    }
}

impl<T> IndexMut<(usize, usize)> for Grid<T> {
    fn index_mut(&mut self, (x, y): (usize, usize)) -> &mut T {
        &mut self.data[y * self.width + x]
    }
}

fn main() {
    let mut grid = Grid {
        data: vec![0; 9],
        width: 3,
        height: 3,
    };

    grid[(1, 1)] = 5;
    println!("{}", grid[(1, 1)]);
}

3.4 Supertraits

A trait can require implementors to also implement another trait:

use std::fmt;

// OutlinePrint requires Display
trait OutlinePrint: fmt::Display {
    fn outline_print(&self) {
        let output = self.to_string();
        let len = output.len();
        println!("{}", "*".repeat(len + 4));
        println!("*{}*", " ".repeat(len + 2));
        println!("* {} *", output);
        println!("*{}*", " ".repeat(len + 2));
        println!("{}", "*".repeat(len + 4));
    }
}

struct Point {
    x: i32,
    y: i32,
}

impl fmt::Display for Point {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        write!(f, "({}, {})", self.x, self.y)
    }
}

impl OutlinePrint for Point {}

fn main() {
    let p = Point { x: 1, y: 2 };
    p.outline_print();
}

3.5 Default Trait Implementation

trait Summary {
    fn summarize_author(&self) -> String;

    // Default implementation
    fn summarize(&self) -> String {
        format!("(Read more from {}...)", self.summarize_author())
    }
}

struct Tweet {
    username: String,
    content: String,
}

impl Summary for Tweet {
    fn summarize_author(&self) -> String {
        format!("@{}", self.username)
    }

    // Can override default
    fn summarize(&self) -> String {
        format!("{}: {}", self.username, self.content)
    }
}

struct Article {
    author: String,
    headline: String,
}

impl Summary for Article {
    fn summarize_author(&self) -> String {
        self.author.clone()
    }

    // Uses default implementation
}

3.6 Extension Traits

Add methods to existing types without modifying them:

// Extend Iterator with custom methods
trait IteratorExt: Iterator {
    fn my_sum(self) -> Self::Item
    where
        Self: Sized,
        Self::Item: std::ops::Add<Output = Self::Item> + Default,
    {
        self.fold(Self::Item::default(), |acc, x| acc + x)
    }

    fn my_collect_vec(self) -> Vec<Self::Item>
    where
        Self: Sized,
    {
        let mut vec = Vec::new();
        for item in self {
            vec.push(item);
        }
        vec
    }
}

// Blanket implementation for all iterators
impl<T: Iterator> IteratorExt for T {}

fn main() {
    let sum = (1..=5).my_sum();
    println!("Sum: {}", sum);

    let vec = (1..=5).my_collect_vec();
    println!("{:?}", vec);
}

3.7 Marker Traits

Traits with no methods that mark types with special properties:

// Define a marker trait
trait Trusted {}

struct SafeData;
struct UntrustedData;

impl Trusted for SafeData {}

// Generic function that requires Trusted
fn process_trusted<T: Trusted>(data: T) {
    println!("Processing trusted data");
}

fn main() {
    let safe = SafeData;
    process_trusted(safe);  // OK

    let unsafe_data = UntrustedData;
    // process_trusted(unsafe_data);  // ERROR: not Trusted
}

// Standard library examples:
// - Send: type can be transferred across thread boundaries
// - Sync: type can be referenced from multiple threads
// - Copy: bitwise copy is safe
// - Sized: size known at compile time

3.8 Async Traits (Using async-trait)

While async fn in traits is stabilizing, here's how to use them today:

use async_trait::async_trait;

#[async_trait]
trait AsyncDatabase {
    async fn query(&self, sql: &str) -> Result<Vec<String>, String>;
    async fn execute(&mut self, sql: &str) -> Result<(), String>;
}

struct PostgresDB {
    connection: String,
}

#[async_trait]
impl AsyncDatabase for PostgresDB {
    async fn query(&self, sql: &str) -> Result<Vec<String>, String> {
        // Simulate async query
        tokio::time::sleep(tokio::time::Duration::from_millis(100)).await;
        Ok(vec![format!("Result from: {}", sql)])
    }

    async fn execute(&mut self, sql: &str) -> Result<(), String> {
        println!("Executing: {}", sql);
        Ok(())
    }
}

#[tokio::main]
async fn main() {
    let db = PostgresDB {
        connection: "localhost".to_string(),
    };

    let results = db.query("SELECT * FROM users").await.unwrap();
    println!("{:?}", results);
}

3.9 Derive Macros

Common traits can be automatically derived:

#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
struct User {
    id: u64,
    name: String,
    email: String,
}

// Custom derive with derive_more crate
use derive_more::{Add, Display, From};

#[derive(Debug, Add, Display, From)]
struct Money(u64);

fn main() {
    let m1 = Money(100);
    let m2 = Money(50);
    let total = m1 + m2;

    println!("{}", total);  // Display
}

3.10 Chapter Summary

Key Takeaways