Implement Traits

Need to work with traits in Rust? This guide covers defining traits, implementing standard traits, using trait bounds, and advanced trait patterns.

Problem: Defining Shared Behavior

Scenario

Multiple types need to share common behavior.

Solution: Define a Trait

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

pub struct Article {
    pub title: String,
    pub author: String,
    pub content: String,
}

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

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

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

fn main() {
    let article = Article {
        title: String::from("Rust Programming"),
        author: String::from("Alice"),
        content: String::from("..."),
    };

    let tweet = Tweet {
        username: String::from("bob"),
        content: String::from("Hello world!"),
    };

    println!("{}", article.summarize());
    println!("{}", tweet.summarize());
}

Problem: Default Trait Implementations

Scenario

You want to provide default behavior that can be overridden.

Solution: Implement Methods in Trait Definition

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

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

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

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

pub struct Article {
    pub title: String,
    pub author: String,
}

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

    // Override default implementation
    fn summarize(&self) -> String {
        format!("{} by {}", self.title, self.author)
    }
}

Problem: Function Taking Multiple Types

Scenario

Function should accept any type implementing a trait.

Solution 1: Trait Bounds with impl Trait

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

// Also accepts multiple different types
pub fn compare(item1: &impl Summary, item2: &impl Summary) {
    println!("Item 1: {}", item1.summarize());
    println!("Item 2: {}", item2.summarize());
}

Solution 2: Generic Type Parameters

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

// Require both parameters to be same type
pub fn compare<T: Summary>(item1: &T, item2: &T) {
    println!("Item 1: {}", item1.summarize());
    println!("Item 2: {}", item2.summarize());
}

Solution 3: Multiple Trait Bounds

use std::fmt::Display;

pub fn notify<T: Summary + Display>(item: &T) {
    println!("{}", item);
    println!("Summary: {}", item.summarize());
}

// Where clause for readability
pub fn complex_function<T, U>(t: &T, u: &U) -> String
where
    T: Display + Clone,
    U: Clone + Debug,
{
    // Function body
    format!("t: {}", t)
}

Problem: Returning Types Implementing Traits

Scenario

Function returns different types that share a trait.

Solution 1: impl Trait Return Type

fn returns_summarizable(switch: bool) -> impl Summary {
    // Error: cannot return different types
    if switch {
        Article { /* ... */ }
    } else {
        Tweet { /* ... */ }  // Compile error
    }
}

Note: impl Trait only works when returning single concrete type.

Solution 2: Box for Dynamic Dispatch

fn returns_summarizable(switch: bool) -> Box<dyn Summary> {
    if switch {
        Box::new(Article {
            title: String::from("Title"),
            author: String::from("Author"),
            content: String::from("Content"),
        })
    } else {
        Box::new(Tweet {
            username: String::from("user"),
            content: String::from("tweet"),
        })
    }
}

Trade-off: Runtime cost for dynamic dispatch, but more flexibility.


Problem: Implementing Standard Library Traits

Scenario

You want your type to work with standard Rust features.

Solution: Implement Common Traits

Debug - Printable for debugging:

use std::fmt;

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

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

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

Display - User-facing output:

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

Clone - Explicit duplication:

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

// Or manual implementation
impl Clone for Point {
    fn clone(&self) -> Self {
        Point {
            x: self.x,
            y: self.y,
        }
    }
}

PartialEq and Eq - Equality comparison:

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

// Manual implementation
impl PartialEq for Point {
    fn eq(&self, other: &Self) -> bool {
        self.x == other.x && self.y == other.y
    }
}

PartialOrd and Ord - Ordering:

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

// Manual implementation
impl PartialOrd for Point {
    fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
        Some(self.cmp(other))
    }
}

impl Ord for Point {
    fn cmp(&self, other: &Self) -> std::cmp::Ordering {
        self.x.cmp(&other.x)
            .then(self.y.cmp(&other.y))
    }
}

Problem: Custom Operators

Scenario

You want to use operators like + with your types.

Solution: Implement Operator Traits

Add trait for + operator:

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() {
    let p1 = Point { x: 1, y: 2 };
    let p2 = Point { x: 3, y: 4 };
    let p3 = p1 + p2;
    assert_eq!(p3, Point { x: 4, y: 6 });
}

Other operators:

use std::ops::{Sub, Mul, Div};

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

impl Mul<i32> for Point {
    type Output = Point;
    fn mul(self, scalar: i32) -> Point {
        Point {
            x: self.x * scalar,
            y: self.y * scalar,
        }
    }
}

Problem: Converting Between Types

Scenario

You need to convert between your types.

Solution: Implement From and Into

From trait:

struct Celsius(f64);
struct Fahrenheit(f64);

impl From<Celsius> for Fahrenheit {
    fn from(c: Celsius) -> Self {
        Fahrenheit(c.0 * 9.0 / 5.0 + 32.0)
    }
}

fn main() {
    let c = Celsius(100.0);
    let f: Fahrenheit = c.into();  // Into automatically provided
    // Or explicitly
    let f = Fahrenheit::from(Celsius(100.0));
}

TryFrom for fallible conversions:

use std::convert::TryFrom;

struct PositiveInt(u32);

impl TryFrom<i32> for PositiveInt {
    type Error = &'static str;

    fn try_from(value: i32) -> Result<Self, Self::Error> {
        if value >= 0 {
            Ok(PositiveInt(value as u32))
        } else {
            Err("Value must be positive")
        }
    }
}

fn main() {
    let pos = PositiveInt::try_from(5).unwrap();
    let neg = PositiveInt::try_from(-5);  // Err("Value must be positive")
}

Problem: Iterator for Custom Type

Scenario

You want your type to be iterable.

Solution: Implement Iterator Trait

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

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

impl Iterator 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 main() {
    let counter = Counter::new(5);
    for num in counter {
        println!("{}", num);
    }
    // Output: 1, 2, 3, 4, 5
}

IntoIterator for ownership-taking iteration:

struct MyVec {
    items: Vec<i32>,
}

impl IntoIterator for MyVec {
    type Item = i32;
    type IntoIter = std::vec::IntoIter<i32>;

    fn into_iter(self) -> Self::IntoIter {
        self.items.into_iter()
    }
}

fn main() {
    let my_vec = MyVec { items: vec![1, 2, 3] };
    for item in my_vec {
        println!("{}", item);
    }
}

Problem: Associated Types vs Generic Parameters

Scenario

You’re unsure whether to use associated types or generics.

Solution: Use Associated Types for Single Implementation

Associated type (one implementation per type):

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

impl Iterator for Counter {
    type Item = u32;  // Counter always yields u32
    fn next(&mut self) -> Option<u32> {
        // Implementation
    }
}

Generic parameter (multiple implementations possible):

trait From<T> {
    fn from(value: T) -> Self;
}

// String can implement From<&str>, From<Vec<u8>>, etc.
impl From<&str> for String { /* ... */ }
impl From<Vec<u8>> for String { /* ... */ }

Guideline: Use associated types when there’s one natural choice. Use generics when type could have multiple valid implementations.


Problem: Trait Objects and Dynamic Dispatch

Scenario

You need a collection of different types implementing same trait.

Solution: Use dyn Trait

trait Draw {
    fn draw(&self);
}

struct Circle {
    radius: f64,
}

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

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

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

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

    for shape in shapes {
        shape.draw();
    }
}

Requirements for trait objects:

  • Methods cannot return Self
  • Methods cannot have generic type parameters
  • Trait must be object-safe

Common Pitfalls

Pitfall 1: Forgetting to Import Trait

Problem: Trait methods not available.

// Error: no method named `summarize` found
let article = Article { /* ... */ };
article.summarize();

Solution: Import the trait.

use crate::Summary;  // Now methods available

Pitfall 2: Orphan Rule Violation

Problem: Cannot implement external trait for external type.

// Error: cannot implement Display for Vec<T>
impl Display for Vec<String> { /* ... */ }

Solution: Create a newtype wrapper.

struct MyVec(Vec<String>);

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

Pitfall 3: Not Using Where Clauses

Problem: Unreadable type bounds.

// Hard to read
fn function<T: Display + Clone, U: Clone + Debug, V: Display + Debug>(t: T, u: U, v: V) {
}

Solution: Use where clause.

fn function<T, U, V>(t: T, u: U, v: V)
where
    T: Display + Clone,
    U: Clone + Debug,
    V: Display + Debug,
{
}

Related Resources


Master traits for flexible, reusable Rust code!

Last updated