Traits in Rust(Interface)

Traits in Rust are a way to define shared behavior across different types. They are similar to interfaces in other languages but with additional capabilities. Traits allow you to define methods that types must implement, enabling polymorphism and code reuse.

Defining a Trait

A trait is defined using the trait keyword, followed by the trait name and a block of method signatures.

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

This defines a Summary trait with one method summarize that takes &self and returns a String.

Implementing a Trait

To implement a trait for a type, use the impl keyword followed by the trait name for the type.

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

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

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

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

Now, both NewsArticle and Tweet implement the Summary trait.

Default Implementations

You can provide default implementations for trait methods. Types can override them if needed.

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

impl Summary for NewsArticle {} // Uses default implementation

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

Trait Bounds

Trait bounds specify that a generic type must implement certain traits.

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

fn main() {
    let tweet = Tweet {
        username: String::from("horse_ebooks"),
        content: String::from("of course, as you probably already know, people"),
        reply: false,
        retweet: false,
    };

    notify(&tweet);
}

Here, T: Summary means T must implement Summary.

Using Traits as Parameters

Traits can be used as function parameters directly with impl Trait syntax.

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

This is syntactic sugar for trait bounds.

Returning Types that Implement Traits

You can return types that implement a trait.

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,
    }
}

The return type is some type that implements Summary, but the caller doesn’t need to know which type.

Trait Objects

For dynamic dispatch, use trait objects with &dyn Trait.

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

fn main() {
    let tweet = Tweet { /* ... */ };
    let article = NewsArticle { /* ... */ };

    notify(&tweet);
    notify(&article);
}

This allows different types implementing the trait to be used interchangeably at runtime.

Multiple Trait Bounds

A type can be required to implement multiple traits.

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

fn compare_and_print<T: Display + Debug>(item: &T) {
    println!("Item: {}", item);
    println!("Debug: {:?}", item);
}

Or using where clause for readability:

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

Summary

Traits are fundamental to Rust’s type system, enabling:

  • Shared behavior definitions
  • Polymorphism
  • Code reuse
  • Type safety

They allow you to write flexible, generic code while maintaining compile-time guarantees. Common traits in the standard library include Clone, Copy, Debug, Display, and Default.