What are Traits in Rust? A well-known concept you might already know

Francesco Ciulla - Jul 16 - - Dev Community

Understanding Traits in Rust

Traits might sound new, but you've probably encountered the concept before. Think of them like interfaces in other languages – they define shared behavior in a way that multiple types can use. Let's break down how traits work and why they're helpful.

What is a Trait?

A trait in Rust defines functionality that a particular type has and can share with others. It specifies methods that can be called on a type. For example, imagine we have different types of text data: a NewsArticle struct for news stories and a Tweet struct for tweets. Both can share a typical behavior: summarizing content. We define this shared behavior using a trait.

pub trait Summary {
    fn summarize(&self) -> String;
}
Enter fullscreen mode Exit fullscreen mode

Here, the Summary trait has a summarize method. Any type implementing this trait must provide its own version of this method.

Implementing Traits

To implement a trait, you define the method specified by the trait for your type. Here's how we do it for NewsArticle and Tweet.

pub 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)
    }
}

pub 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)
    }
}
Enter fullscreen mode Exit fullscreen mode

Now, both NewsArticle and Tweet can use the summarize method. This allows us to call summarize on instances of these types.

Default Implementations

Traits can also have default method implementations. If we don't want to write the same method for each type, we can provide a default:

pub trait Summary {
    fn summarize(&self) -> String {
        String::from("(Read more...)")
    }
}
Enter fullscreen mode Exit fullscreen mode

Types can still override this default if they need specific behavior.

Using Traits as Parameters

Traits can be used to define function parameters. If we want a function to accept any type that implements Summary, we can write:

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

This makes our code flexible and reusable.

Returning Types that Implement Traits

We can also specify that a function returns a type that implements 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,
    }
}
Enter fullscreen mode Exit fullscreen mode

This allows us to return different types that conform to the Summary trait without exposing the concrete type.

Conditional Implementations

Sometimes, you want methods to be available only if certain conditions are met. Rust allows conditional implementations:

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);
        }
    }
}
Enter fullscreen mode Exit fullscreen mode

Here, cmp_display is only available if T implements both Display and PartialOrd.

Conclusion

Traits are a powerful feature in Rust that help define and share behavior across types. They make your code more modular, reusable, and easier to understand. By moving errors to compile time, they ensure your code is robust and efficient. Happy coding!

I just released a video about this topic, if you are curious, you can check it out.

If you prefer a video version

. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .