What are Traits in Rust? A well-known concept you might already know
Tags: programming, opensource, rust, beginners
Published: 2024-07-16 15:26:59

## 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.

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

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.

```rust
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)
    }
}
```

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:

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

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:

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

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:

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

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:

```rust
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);
        }
    }
}
```

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
{% youtube w8lmMaKY3Hs %}