Extension Traits
An extension trait is a local trait designed specifically to add new convenience methods to an existing external type—such as a primitive (str, [T]), a standard library struct (Result<T, E>, Option<T>), or a trait (Iterator)—without modifying the original crate.
Why Extension Traits Work
Rust forbids adding inherent methods (impl str { ... } or impl<T> Vec<T> { ... }) to types defined outside your crate. However, the orphan rule permits implementing a local trait for any external type. When callers import the extension trait (use crate::StrExt;), its methods become callable with standard dot syntax ("text".method()).
External Type (`str` in `core`) Local Extension Trait (`SlugExt`)
+-----------------------------+ +----------------------------------+
| Inherent methods are fixed | +---> | pub trait SlugExt { |
| by standard library | | fn to_slug(&self) -> String; |
+-----------------------------+ | } |
| impl SlugExt for str { ... } |
+----------------------------------+
By convention, extension traits are named with an Ext suffix (such as SliceExt, IteratorExt, ReadExt, or FutureExt).
Extending str and Slices [T]
Implementing an extension trait directly on the unsized slice types str and [T] makes the methods callable on &str, String, &[T], Vec<T>, and [T; N] simultaneously via auto-deref:
pub trait StrExt {
fn to_kebab_case(&self) -> String;
}
impl StrExt for str {
fn to_kebab_case(&self) -> String {
self.split_whitespace()
.map(str::to_ascii_lowercase)
.collect::<Vec<_>>()
.join("-")
}
}
fn main() {
let literal: &str = "Rust Extension Traits";
let owned: String = String::from("Zero Cost Abstraction");
println!("{}", literal.to_kebab_case());
println!("{}", owned.to_kebab_case());
}
rust-extension-traits
zero-cost-abstraction
Blanket Extension Traits Over Another Trait (IteratorExt)
Combining an extension trait with a supertrait bound and a blanket implementation attaches new adapter or terminal methods to every type implementing a base trait (such as Iterator):
pub trait IteratorExt: Iterator {
/// Counts how many items satisfy a predicate while consuming the iterator.
fn count_where<P>(self, mut predicate: P) -> usize
where
Self: Sized,
P: FnMut(&Self::Item) -> bool,
{
let mut count = 0;
for item in self {
if predicate(&item) {
count += 1;
}
}
count
}
}
// Blanket implementation: EVERY `I: Iterator` automatically implements `IteratorExt`
impl<I: Iterator + ?Sized> IteratorExt for I {}
fn main() {
let numbers = [3, -1, 8, 0, -5, 12];
let positive_count = numbers.iter().count_where(|&&n| n > 0);
println!("Positive numbers: {positive_count}");
}
Positive numbers: 3
Notice two idiomatic details in IteratorExt:
- Every method bodies lives directly inside
trait IteratorExtas a default implementation, soimpl<I: Iterator + ?Sized> IteratorExt for I {}has an empty body{}. - Sealing
IteratorExt(preventing downstream crates from writing customimpl IteratorExtoverrides) ensures no external type can override the extension method with inconsistent behavior.