Traits in Rust 🦀

Anoop P Oommen
4 min readDec 11, 2022

An explanation to traits in rust and usage

Traits are an important concept in the Rust programming language, and understanding them is crucial for writing correct and efficient code. In this article, we’ll cover the basics of what traits are and how they are used in Rust.

At its core, a trait is a collection of methods that a type can implement. This is similar to an interface in other languages, but unlike an interface, a trait can also define default implementations for some or all of its methods. This allows for more flexibility and code reuse.

To use a trait, you first need to define it. This is done using the trait keyword, followed by the name of the trait and the methods it defines. Here's an example

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

This trait defines a single method, summarize, which takes a reference to self and returns a string. Note that this method is not implemented in the trait itself, so any type that implements this trait must provide its own implementation of the summarize method.

To implement a trait for a type, you use the impl keyword, followed by the trait name and the type you want to implement it for. Here's an example

struct NewsArticle {
headline: String,
body: String,
}

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

In this example, we’ve defined a struct called NewsArticle that has two fields: headline and body. We've also implemented the Summary trait for the NewsArticle type by providing an implementation for the summarize method. This means that any NewsArticle now has the summarize method available, and can be used to generate a summary of the article.

One of the key benefits of using traits is that they allow for polymorphism. This means that you can write code that can operate on multiple types that implement the same trait, without needing to know the specific type at compile time. Here’s an example

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

In this example, the summarize function takes any type that implements the Summary trait as its argument. This allows us to call the summarize function with any type that has the Summary trait implemented for it, such as a NewsArticle or any other type that implements Summary.

Overall, traits are an essential part of the Rust language, and understanding how to use them is crucial for writing correct and efficient code. They allow for code reuse and polymorphism, and are a powerful tool for creating flexible and modular code.

A little bit more on traits 🦀

Now that we’ve covered the basics of traits in Rust, let’s take a look at some more advanced uses for them.

One common use for traits is to define default implementations for methods. This allows you to provide a basic implementation for a method in a trait, and then allow types that implement the trait to override that implementation if needed. Here’s an example

trait Summary {
fn summarize(&self) -> String {
format!("[{}]", self.headline())
}

fn headline(&self) -> String;
}

In this example, the Summary trait defines a default implementation for the summarize method. This implementation simply returns a string in the format "[headline]", where headline is the result of calling the headline method on the self parameter. The headline method is not implemented in the trait itself, so any type that implements the Summary trait must provide its own implementation of this method.

This default implementation can be overridden by any type that implements the Summary trait. For example

struct NewsArticle {
headline: String,
body: String,
}

impl Summary for NewsArticle {
fn headline(&self) -> String {
self.headline.clone()
}

fn summarize(&self) -> String {
format!("{}: {}", self.headline(), self.body)
}
}

In this example, we’ve defined a struct called NewsArticle that has two fields: headline and body. We've also implemented the Summary trait for the NewsArticle type. In the implementation, we've provided our own versions of the headline and summarize methods. The headline method simply returns a copy of the headline field from the NewsArticle struct. The summarize method overrides the default implementation from the Summary trait and provides a more detailed summary of the NewsArticle.

Another advanced use for traits is to use them as trait bounds for generic types. This allows you to specify that a generic type must implement a certain trait in order to be used with certain functions or methods. Here’s an example

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

In this example, the summarize function is generic over the type T. The T: Summary syntax is called a trait bound, and it specifies that the type T must implement the Summary trait in order to be used with the summarize function. This means that you can only call the summarize function with types that implement the Summary trait.

Trait bounds can also be used to specify multiple traits. For example

fn summarize<T: Summary + Clone>(item: T) {
let s = item.summarize();
let c = item.clone();
println!("{}: {}", s, c);
}

In this example, the summarize function is generic over the type T, and it has a trait bound that specifies that T must implement both the Summary and Clone traits. This means that you can only call the summarize unction with types that implement both the Summary and Clone traits. This allows the function to use both the summarize and clone methods on the item parameter.

Overall, traits in Rust are a powerful tool for defining common behavior that multiple types can implement. They allow for code reuse, polymorphism, and the ability to specify trait bounds for generic types. These advanced uses of traits can help you write more flexible and modular code in Rust.

--

--