Traits: Defining shared behavior

Traits in Rust allow us to define shared behavior for a particular type and ensure that any concrete type implementing this trait provides the required functionality. This helps reduce duplication and improves code maintainability.

Defining a trait

To define a trait, we use the pub trait keyword followed by the name of the trait. For example:

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

In this example, we're defining a trait called Summary that requires any implementing type to provide an implementation for the summarize method.

Implementing a trait on a type

To implement a trait on a specific type, we use the impl keyword followed by the name of the trait. For example:

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

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

In this example, we're implementing the Summary trait on the NewsArticle struct. The implementation provides a specific way to summarize a news article.

Default implementations

Traits can also provide default implementations for methods. This allows us to provide a fallback behavior if a type doesn't implement the method itself. For example:

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

impl Summary for i32 {
    fn summarize(&self) -> String {
        "No summary available".to_string()
    }
}

In this example, we're providing a default implementation for the Summary trait on the i32 type. If any concrete type implementing Summary doesn't provide its own implementation, it will fall back to this default behavior.

Traits as parameters

Traits can also be used as parameters in functions and methods. This allows us to constrain the types that can be passed as arguments to a function or method. For example:

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

In this example, we're defining a notify function that takes an item of type &impl Summary, which means it can take any type that implements the Summary trait. The function then calls the summarize method on the item and prints the result.

Example Code:

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

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

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

fn main() {
    let article = NewsArticle {
        headline: "Rust Programming Language".to_string(),
        author: "The Author".to_string(),
        location: "Location".to_string(),
    };

    println!("{}", article.summarize()); // Output: Rust Programming Language - The Author
}

Output:

Rust Programming Language - The Author