Advanced Level

Advanced Traits

Chapter 18: Advanced Traits šŸš€

Explore sophisticated trait features like associated types, default generic types, fully qualified syntax, and supertraits.

Associated Types

Defining Associated Types

Associated types allow traits to define placeholders for types that are filled in by implementors:

trait Iterator {
    type Item;
    
    fn next(&mut self) -> Option<Self::Item>;
}

struct Counter {
    count: u32,
}

impl Iterator for Counter {
    type Item = u32;
    
    fn next(&mut self) -> Option<Self::Item> {
        self.count += 1;
        
        if self.count < 6 {
            Some(self.count)
        } else {
            None
        }
    }
}

Associated Types vs Generic Types

Without associated types (verbose):

trait IteratorGeneric<T> {
    fn next(&mut self) -> Option<T>;
}

With associated types (cleaner):

trait Iterator {
    type Item;
    
    fn next(&mut self) -> Option<Self::Item>;
}

Default Generic Type Parameters

Operator Overloading

use std::ops::Add;

#[derive(Debug, PartialEq)]
struct Point {
    x: i32,
    y: i32,
}

impl Add for Point {
    type Output = Point;
    
    fn add(self, other: Point) -> Point {
        Point {
            x: self.x + other.x,
            y: self.y + other.y,
        }
    }
}

fn main() {
    assert_eq!(
        Point { x: 1, y: 0 } + Point { x: 2, y: 3 },
        Point { x: 3, y: 3 }
    );
}

Custom Add Implementation with Default Type Parameter

use std::ops::Add;

trait Adder<Rhs = Self> {
    type Output;
    
    fn add(self, rhs: Rhs) -> Self::Output;
}

#[derive(Debug, Clone, Copy)]
struct Millimeters(u32);

#[derive(Debug, Clone, Copy)]
struct Meters(u32);

impl Add<Meters> for Millimeters {
    type Output = Millimeters;
    
    fn add(self, other: Meters) -> Millimeters {
        Millimeters(self.0 + (other.0 * 1000))
    }
}

impl Add for Millimeters {
    type Output = Millimeters;
    
    fn add(self, other: Millimeters) -> Millimeters {
        Millimeters(self.0 + other.0)
    }
}

Fully Qualified Syntax for Disambiguation

Calling Methods with Same Name

trait Pilot {
    fn fly(&self);
}

trait Wizard {
    fn fly(&self);
}

struct Human;

impl Pilot for Human {
    fn fly(&self) {
        println!("This is your captain speaking.");
    }
}

impl Wizard for Human {
    fn fly(&self) {
        println!("Up!");
    }
}

impl Human {
    fn fly(&self) {
        println!("*waving arms furiously*");
    }
}

fn main() {
    let person = Human;
    
    // Calls the inherent method
    person.fly();
    
    // Fully qualified syntax to disambiguate
    Pilot::fly(&person);
    Wizard::fly(&person);
    
    // Alternative syntax using <Type as Trait>::method
    <Human as Pilot>::fly(&person);
    <Human as Wizard>::fly(&person);
}

Supertraits

Requiring One Trait in Another

use std::fmt;

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

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

impl OutlinePrint for Point {} // Error: Point doesn't implement Display

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

impl OutlinePrint for Point {} // Now it works!

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

Newtype Pattern for Type Safety

Wrapper Types

use std::fmt;

struct Wrapper(Vec<String>);

impl fmt::Display for Wrapper {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        write!(f, "[{}]", self.0.join(", "))
    }
}

fn main() {
    let w = Wrapper(vec![
        String::from("hello"),
        String::from("world"),
    ]);
    println!("w = {}", w);
}

Practical Examples

Example 1: Custom Database Connection Trait

use std::fmt::Display;

// Supertrait that requires Display
trait DatabaseConnection: Display {
    type Error: std::error::Error;
    type Row;
    
    fn connect(url: &str) -> Result<Self, Self::Error>
    where
        Self: Sized;
    
    fn execute_query(&self, query: &str) -> Result<Vec<Self::Row>, Self::Error>;
    
    fn close(&mut self);
}

#[derive(Debug)]
struct PostgresConnection {
    url: String,
    connected: bool,
}

#[derive(Debug)]
struct PostgresError {
    message: String,
}

impl std::error::Error for PostgresError {}

impl Display for PostgresError {
    fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
        write!(f, "PostgreSQL Error: {}", self.message)
    }
}

impl Display for PostgresConnection {
    fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
        write!(f, "PostgreSQL Connection to {}", self.url)
    }
}

impl DatabaseConnection for PostgresConnection {
    type Error = PostgresError;
    type Row = String;
    
    fn connect(url: &str) -> Result<Self, Self::Error> {
        // Simulate connection logic
        if url.is_empty() {
            Err(PostgresError {
                message: String::from("URL cannot be empty"),
            })
        } else {
            Ok(PostgresConnection {
                url: url.to_string(),
                connected: true,
            })
        }
    }
    
    fn execute_query(&self, query: &str) -> Result<Vec<Self::Row>, Self::Error> {
        if !self.connected {
            return Err(PostgresError {
                message: String::from("Not connected to database"),
            });
        }
        
        if query.is_empty() {
            return Err(PostgresError {
                message: String::from("Query cannot be empty"),
            });
        }
        
        // Simulate query execution
        Ok(vec![
            String::from("row1"),
            String::from("row2"),
            String::from("row3"),
        ])
    }
    
    fn close(&mut self) {
        self.connected = false;
        println!("PostgreSQL connection closed");
    }
}

fn main() {
    match PostgresConnection::connect("postgresql://localhost:5432/mydb") {
        Ok(conn) => {
            println!("Connected: {}", conn);
            
            match conn.execute_query("SELECT * FROM users") {
                Ok(rows) => {
                    for row in rows {
                        println!("Row: {}", row);
                    }
                }
                Err(e) => println!("Error: {}", e),
            }
        }
        Err(e) => println!("Connection failed: {}", e),
    }
}

Example 2: Advanced Iterator Trait Implementation

trait AdvancedIterator {
    type Item;
    
    fn next(&mut self) -> Option<Self::Item>;
    
    fn size_hint(&self) -> (usize, Option<usize>) {
        (0, None)
    }
    
    fn count(self) -> usize
    where
        Self: Sized,
    {
        let mut count = 0;
        let mut iter = self;
        while let Some(_) = iter.next() {
            count += 1;
        }
        count
    }
    
    fn map<B, F>(self, f: F) -> Map<Self, F>
    where
        Self: Sized,
        F: FnMut(Self::Item) -> B,
    {
        Map { iter: self, f }
    }
}

struct Map<I, F> {
    iter: I,
    f: F,
}

impl<B, I, F> AdvancedIterator for Map<I, F>
where
    I: AdvancedIterator,
    F: FnMut(I::Item) -> B,
{
    type Item = B;
    
    fn next(&mut self) -> Option<Self::Item> {
        self.iter.next().map(&mut self.f)
    }
}

struct Counter {
    count: u32,
    max: u32,
}

impl Counter {
    fn new(max: u32) -> Counter {
        Counter { count: 0, max }
    }
}

impl AdvancedIterator for Counter {
    type Item = u32;
    
    fn next(&mut self) -> Option<Self::Item> {
        if self.count < self.max {
            self.count += 1;
            Some(self.count)
        } else {
            None
        }
    }
    
    fn size_hint(&self) -> (usize, Option<usize>) {
        let remaining = (self.max - self.count) as usize;
        (remaining, Some(remaining))
    }
}

fn main() {
    let counter = Counter::new(5);
    println!("Count: {}", counter.count());
    
    let counter = Counter::new(3);
    let doubled: Vec<u32> = counter.map(|x| x * 2).collect();
    println!("Doubled: {:?}", doubled);
}

Common Mistakes

āŒ Forgetting Supertrait Requirements

trait OutlinePrint {
    fn outline_print(&self) {
        let output = self.to_string(); // Error: to_string() not available
        // ...
    }
}

āœ… Properly Implementing Supertraits

use std::fmt::Display;

trait OutlinePrint: Display {
    fn outline_print(&self) {
        let output = self.to_string(); // Now works because Display is required
        // ...
    }
}

āŒ Incorrect Associated Type Implementation

trait Container {
    type Item;
    fn insert(&mut self, item: Self::Item);
    fn remove(&mut self) -> Option<Self::Item>;
}

struct MyContainer {
    items: Vec<String>,
}

impl Container for MyContainer {
    type Item = i32; // Mismatch with Vec<String>
    
    fn insert(&mut self, item: Self::Item) {
        // Error: trying to insert i32 into Vec<String>
        self.items.push(item);
    }
    
    fn remove(&mut self) -> Option<Self::Item> {
        // Error: trying to return String as i32
        self.items.pop()
    }
}

āœ… Correct Associated Type Implementation

impl Container for MyContainer {
    type Item = String;
    
    fn insert(&mut self, item: Self::Item) {
        self.items.push(item);
    }
    
    fn remove(&mut self) -> Option<Self::Item> {
        self.items.pop()
    }
}

Key Takeaways

  • āœ… Associated types allow traits to define placeholders for types
  • āœ… Default generic type parameters make traits more flexible
  • āœ… Fully qualified syntax resolves method name ambiguities
  • āœ… Supertraits require other traits to be implemented first
  • āœ… The newtype pattern provides type safety and encapsulation
  • āœ… Advanced traits enable powerful abstractions in Rust
  • āœ… Follow Rust naming conventions and idioms for trait usage

Ready for Chapter 19? → Unsafe Rust

šŸ¦€ Rust Programming Tutorial

Learn from Zero to Advanced

Built with Next.js and Tailwind CSS • Open Source