Rust by Example: Traits and Implementations
Rust 1.75+
Define shared behavior across different types using Traits. Similar to interfaces in other languages, this example shows how to define traits, implement them for types, and use them to write polymorphic code.
Code
// Define a trait (like an interface)
trait Summary {
fn summarize(&self) -> String;
// Default implementation
fn greeting(&self) -> String {
String::from("Hello!")
}
}
struct NewsArticle {
headline: String,
location: String,
author: String,
}
// Implement trait for NewsArticle
impl Summary for NewsArticle {
fn summarize(&self) -> String {
format!("{}, by {} ({})", self.headline, self.author, self.location)
}
}
struct Tweet {
username: String,
content: String,
}
// Implement trait for Tweet
impl Summary for Tweet {
fn summarize(&self) -> String {
format!("{}: {}", self.username, self.content)
}
}
// Function that accepts any type implementing Summary
fn notify(item: &impl Summary) {
println!("Breaking News! {}", item.summarize());
}
fn main() {
let article = NewsArticle {
headline: String::from("Penguins win the Stanley Cup Championship!"),
location: String::from("Pittsburgh, PA, USA"),
author: String::from("Iceburgh"),
};
let tweet = Tweet {
username: String::from("horse_ebooks"),
content: String::from("of course, as you probably already know, people"),
};
println!("Article: {}", article.summarize());
println!("Tweet: {}", tweet.summarize());
// Using default implementation
println!("Greeting: {}", tweet.greeting());
notify(&article);
}Explanation
Traits define shared behavior in an abstract way, similar to interfaces in other languages. They are the backbone of Rust's polymorphism. Rust supports two main ways to use traits:
- Static Dispatch: Using generics (e.g.,
fn notify<T: Summary>(item: &T)). The compiler generates specialized code for each concrete type. This is the default and most performant. - Dynamic Dispatch: Using Trait Objects (e.g.,
&dyn Summary). The compiler generates a vtable (virtual method table) and resolves method calls at runtime. This allows for heterogeneous collections but incurs a small runtime cost.
Rust enforces the Orphan Rule (Coherence) to ensure consistency: you can only implement a trait for a type if either the trait or the type is local to your crate. This prevents other crates from breaking your code by adding conflicting implementations.
Code Breakdown
2
trait Summary. This defines the contract. Any type that claims to be a Summary MUST implement the summarize method.18
impl Summary for NewsArticle. This syntax binds the trait to the struct. Inside this block, we provide the specific logic for summarizing a news article.38
fn notify(item: &impl Summary). This is syntactic sugar for fn notify(item: &T) . It uses static dispatch, meaning a separate version of notify is compiled for NewsArticle and Tweet.
