Intermediate Level

Traits

Chapter 12: Traits šŸ”§

Define shared behavior and functionality across different types with traits.

Defining Traits

Basic Trait Definition

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

struct NewsArticle {
    pub headline: String,
    pub location: String,
    pub author: String,
    pub content: String,
}

impl Summary for NewsArticle {
    fn summarize(&self) -> String {
        format!("{}, by {} ({})", self.headline, self.author, self.location)
    }
}

struct Tweet {
    pub username: String,
    pub content: String,
    pub reply: bool,
    pub retweet: bool,
}

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

fn main() {
    let article = NewsArticle {
        headline: String::from("Penguins win the Stanley Cup Championship!"),
        location: String::from("Pittsburgh, PA, USA"),
        author: String::from("Iceburgh"),
        content: String::from("The Pittsburgh Penguins once again are the best hockey team in the NHL."),
    };
    
    let tweet = Tweet {
        username: String::from("horse_ebooks"),
        content: String::from("of course, as you probably already know, people"),
        reply: false,
        retweet: false,
        };
    
    println!("New article available! {}", article.summarize());
    println!("1 new tweet: {}", tweet.summarize());
}

Default Implementations

Providing Default Behavior

trait Summary {
    fn summarize(&self) -> String;
    
    fn summarize_author(&self) -> String {
        String::from("(Read more...)")
    }
}

struct NewsArticle {
    pub headline: String,
    pub location: String,
    pub author: String,
    pub content: String,
}

impl Summary for NewsArticle {
    fn summarize(&self) -> String {
        format!("{}, by {} ({})", self.headline, self.author, self.location)
    }
    
    // Override default implementation
    fn summarize_author(&self) -> String {
        format!("Article by {}", self.author)
    }
}

struct Tweet {
    pub username: String,
    pub content: String,
    pub reply: bool,
    pub retweet: bool,
}

impl Summary for Tweet {
    fn summarize(&self) -> String {
        format!("{}: {}", self.username, self.content)
    }
    
    // Using default implementation for summarize_author
}

fn main() {
    let article = NewsArticle {
        headline: String::from("Penguins win the Stanley Cup Championship!"),
        location: String::from("Pittsburgh, PA, USA"),
        author: String::from("Iceburgh"),
        content: String::from("The Pittsburgh Penguins once again are the best hockey team in the NHL."),
    };
    
    let tweet = Tweet {
        username: String::from("horse_ebooks"),
        content: String::from("of course, as you probably already know, people"),
        reply: false,
        retweet: false,
    };
    
    println!("New article available! {}", article.summarize());
    println!("{}", article.summarize_author());
    
    println!("1 new tweet: {}", tweet.summarize());
    println!("{}", tweet.summarize_author());
}

Traits as Parameters

Trait Bound Syntax

pub trait Summary {
    fn summarize(&self) -> String;
}

pub fn notify(item: &impl Summary) {
    println!("Breaking news! {}", item.summarize());
}

// Alternative syntax with trait bounds
pub fn notify_alt<T: Summary>(item: &T) {
    println!("Breaking news! {}", item.summarize());
}

fn main() {
    let article = NewsArticle {
        headline: String::from("Penguins win the Stanley Cup Championship!"),
        location: String::from("Pittsburgh, PA, USA"),
        author: String::from("Iceburgh"),
        content: String::from("The Pittsburgh Penguins once again are the best hockey team in the NHL."),
    };
    
    notify(&article);
    notify_alt(&article);
}

Multiple Trait Bounds

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

fn some_function<T: Display + Clone, U: Clone + Debug>(t: &T, u: &U) -> i32 {
    // Implementation
    0
}

// Alternative syntax using where clause
fn some_function_alt<T, U>(t: &T, u: & Tangible) -> i32
where
    T: Display + Clone,
    U: Clone + Debug,
{
    // Implementation
    0
}

Returning Types that Implement Traits

impl Trait Syntax

fn returns_summarizable() -> impl Summary {
    Tweet {
        username: String::from("horse_ebooks"),
        content: String::from("of course, as you probably already know, people"),
        reply: false,
        retweet: false,
    }
}

Trait Bounds for Conditional Implementation

Conditional Methods

use std::fmt::Display;

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

impl<T> Pair<T> {
    fn new(x: T, y: T) -> Self {
        Self { x, y }
    }
}

impl<T: Display + PartialOrd> Pair<T> {
    fn cmp_display(&self) {
        if self.x >= self.y {
            println!("The largest member is x = {}", self.x);
        } else {
            println!("The largest member is y = {}", self.y);
        }
    }
}

fn main() {
    let pair = Pair::new(5, 3);
    pair.cmp_display();
    
    let string_pair = Pair::new(String::from("Hello"), String::from("World"));
    string_pair.cmp_display();
}

Blanket Implementations

Implementing for All Types

impl<T: Display> ToString for T {
    // Implementation provided by standard library
}

fn main() {
    let x = 5;
    let s = x.to_string();
    println!("{}", s);
    
    let y = 3.14;
    let t = y.to_string();
    println!("{}", t);
}

Derivable Traits

Common Derivable Traits

#[derive(Debug)]
struct Rectangle {
    width: u32,
    height: u32,
}

#[derive(PartialEq, Eq, Clone, Copy)]
enum Color {
    Red,
    Green,
    Blue,
}

#[derive(PartialEq, PartialOrd)]
struct Temperature(f64);

fn main() {
    let rect = Rectangle { width: 30, height: 50 };
    println!("{:?}", rect);
    
    let color1 = Color::Red;
    let color2 = Color::Red;
    
    if color1 == color2 {
        println!("Colors are equal");
    }
    
    let temp1 = Temperature(25.0);
    let temp2 = Temperature(30.0);
    
    if temp1 < temp2 {
        println!("temp1 is less than temp2");
    }
}

Practical Examples

Example 1: Shape Trait Implementation

trait Shape {
    fn area(&self) -> f64;
    fn perimeter(&self) -> f64;
    fn description(&self) -> String {
        format!("A shape with area {} and perimeter {}", self.area(), self.perimeter())
    }
}

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

impl Shape for Rectangle {
    fn area(&self) -> f64 {
        self.width * self.height
    }
    
    fn perimeter(&self) -> f64 {
        2.0 * (self.width + self.height)
    }
}

struct Circle {
    radius: f64,
}

impl Shape for Circle {
    fn area(&self) -> f64 {
        std::f64::consts::PI * self.radius * self.radius
    }
    
    fn perimeter(&self) -> f64 {
        2.0 * std::f64::consts::PI * self.radius
    }
    
    fn description(&self) -> String {
        format!("A circle with radius {}", self.radius)
    }
}

fn print_shape_info(shape: &impl Shape) {
    println!("{}", shape.description());
}

fn main() {
    let rect = Rectangle { width: 5.0, height: 3.0 };
    let circle = Circle { radius: 4.0 };
    
    print_shape_info(&rect);
    print_shape_info(&circle);
}

Example 2: Payment Processor Trait

trait PaymentProcessor {
    fn process_payment(&self, amount: f64) -> bool;
    fn get_provider_name(&self) -> String;
    fn supports_refund(&self) -> bool {
        false
    }
    fn refund(&self, _amount: f64) -> bool {
        false
    }
}

struct CreditCardProcessor {
    name: String,
}

impl PaymentProcessor for CreditCardProcessor {
    fn process_payment(&self, amount: f64) -> bool {
        println!("Processing ${:.2} payment via Credit Card", amount);
        true // Assume success
    }
    
    fn get_provider_name(&self) -> String {
        self.name.clone()
    }
    
    fn supports_refund(&self) -> bool {
        true
    }
    
    fn refund(&self, amount: f64) -> bool {
        println!("Refunding ${:.2} via Credit Card", amount);
        true
    }
}

struct PayPalProcessor {
    name: String,
}

impl PaymentProcessor for PayPalProcessor {
    fn process_payment(&self, amount: f64) -> bool {
        println!("Processing ${:.2} payment via PayPal", amount);
        true // Assume success
    }
    
    fn get_provider_name(&self) -> String {
        self.name.clone()
    }
}

fn make_payment(processor: &impl PaymentProcessor, amount: f64) {
    if processor.process_payment(amount) {
        println!("Payment successful with {}", processor.get_provider_name());
    } else {
        println!("Payment failed");
    }
}

fn main() {
    let credit_card = CreditCardProcessor {
        name: String::from("Visa"),
    };
    
    let paypal = PayPalProcessor {
        name: String::from("PayPal"),
    };
    
    make_payment(&credit_card, 99.99);
    make_payment(&paypal, 49.99);
    
    if credit_card.supports_refund() {
        credit_card.refund(25.00);
    }
    
    if paypal.supports_refund() {
        paypal.refund(25.00);
    } else {
        println!("PayPal doesn't support refunds in this implementation");
    }
}

Common Mistakes

āŒ Forgetting to Implement Required Methods

trait Summary {
    fn summarize(&self) -> String;
    fn summarize_author(&self) -> String; // No default implementation
}

struct NewsArticle {
    pub headline: String,
    pub location: String,
    pub author: String,
    pub content: String,
}

// This won't compile because summarize_author is not implemented
impl Summary for NewsArticle {
    fn summarize(&self) -> String {
        format!("{}, by {} ({})", self.headline, self.author, self.location)
    }
    // Missing summarize_author implementation
}

āœ… Implementing All Required Methods

impl Summary for NewsArticle {
    fn summarize(&self) -> String {
        format!("{}, by {} ({})", self.headline, self.author, self.location)
    }
    
    fn summarize_author(&self) -> String {
        format!("Article by {}", self.author)
    }
}

Key Takeaways

  • āœ… Traits define shared behavior across types
  • āœ… Default implementations can be provided in traits
  • āœ… Use impl Trait syntax for parameters and return types
  • āœ… Multiple trait bounds can be specified with + or where clauses
  • āœ… Conditional implementations are possible with trait bounds
  • āœ… Blanket implementations apply traits to all matching types
  • āœ… Common derivable traits include Debug, PartialEq, Clone, etc.
  • āœ… Follow Rust naming conventions (trait names are typically adjectives or nouns)

Ready for Chapter 13? → Lifetimes

šŸ¦€ Rust Programming Tutorial

Learn from Zero to Advanced

Built with Next.js and Tailwind CSS • Open Source