Today’s learning was focused on Traits—a powerful abstraction mechanism in Rust that lets you define shared behavior. This post covers everything I explored: from basic definitions to conditional and blanket implementations. Let’s break it down step by step with simple explanations and relevant examples.
🔷 What Are Traits?
Traits in Rust define shared behavior across types, similar to interfaces in languages like JavaScript or TypeScript.
A trait contains method signatures that types can implement. If a type implements a trait, it guarantees that the method(s) are defined with the expected behavior.
pub trait Summary {
fn summarize(&self) -> String;
}
🔧 Implementing Traits on Types
You can implement traits on structs. Let’s take a look at two simple implementations:
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)
}
}
You can similarly implement Summary for other types like a SocialPost.
🚦 Trait Scope Rules
You can only implement a trait on a type if:
- The trait or the type is local to your crate.
- You can’t implement external traits on external types due to the orphan rule. This ensures code from different crates doesn’t conflict.
✅ Valid:
impl Display for MyType {} // Display is external, MyType is local.
❌ Invalid:
impl Display for Vec<T> {} // Both are external.
🧰 Default Implementations
Traits can have default method implementations. Types implementing the trait may override or use these defaults.
pub trait Summary {
fn summarize(&self) -> String {
String::from("(Read more...)")
}
}
This reduces boilerplate, especially when common behavior exists.
🧩 Traits Calling Other Trait Methods
You can build compositional behavior in traits:
pub trait Summary {
fn summarize_author(&self) -> String;
fn summarize(&self) -> String {
format!("(Read more from {}...)", self.summarize_author())
}
}
Then only implement the required piece:
impl Summary for SocialPost {
fn summarize_author(&self) -> String {
format!("@{}", self.username)
}
}
🧬 Traits as Function Parameters
With impl Trait syntax:
pub fn notify(item: &impl Summary) {
println!("Breaking news! {}", item.summarize());
}
With explicit trait bounds:
pub fn notify<T: Summary>(item: &T) {
println!("Breaking news! {}", item.summarize());
}
➕ Multiple Trait Bounds
You can require multiple traits using +:
pub fn notify<T: Summary + Display>(item: &T) {
println!("Info: {}", item);
}
Or using a where clause for better readability:
fn some_function<T, U>(t: &T, u: &U)
where
T: Display + Clone,
U: Clone + Debug,
{}
🔁 Returning Types That Implement Traits
You can return types that implement a trait without naming them:
fn returns_summarizable() -> impl Summary {
SocialPost {
username: String::from("horse_ebooks"),
content: String::from("content..."),
reply: false,
repost: false,
}
}
🛑 You can’t return different types under impl Trait (e.g., either NewsArticle or SocialPost)—they must be the same type.
🧠 Conditional Trait Implementation (Generic + Trait Bounds)
You can conditionally implement methods on types that implement specific traits:
impl<T: Display + PartialOrd> Pair<T> {
fn cmp_display(&self) {
if self.x >= self.y {
println!("x is bigger: {}", self.x);
} else {
println!("y is bigger: {}", self.y);
}
}
}
🌐 Blanket Implementations
You can implement a trait for any type that implements another trait:
impl<T: Display> ToString for T {}
This is how types like i32, f64, or even custom types that implement Display get to_string() for free.
✅ Summary
- Traits allow defining shared behavior like interfaces.
- You can implement traits for structs and enums.
- Traits can provide default implementations.
- Traits can be used as function parameters and return types.
- You can use bounds and where clauses for cleaner generics.
- Conditional and blanket implementations enable powerful patterns.
- Rust enforces safety at compile-time using traits, minimizing runtime surprises.
This wraps up my Day 18 of #100DaysOfRust. Traits are a cornerstone of Rust’s expressive type system. Their power lies in enabling clean, safe, and reusable abstractions.
Top comments (0)