Implementing traits for generic types
A blanket implementation (or "blanket impl") is a trait implementation that applies to all types satisfying certain bounds, rather than implementing the trait for one specific type. They use generic type parameters with trait bounds to provide implementations for entire families of types at once.
Blanket implementations are the mechanism behind some of Rust's most powerful abstractions, enabling automatic trait derivation, generic conversions, and extension traits without requiring per-type boilerplate.use std::fmt::Display;
// Specific implementation - only for String
impl SomeTraitForString for String {
fn method(&self) { /* ... */ }
}
// Blanket implementation - for ALL types that implement Display
impl<T: Display> ToString for T {
fn to_string(&self) -> String {
format!("{}", self)
}
}
// Now EVERY type with Display automatically gets ToString:
let num: i32 = 42;
let s = num.to_string(); // Works! i32 implements Display, gets ToString for free
let pi: f64 = 3.14159;
let s = pi.to_string(); // Works! f64 implements Display, gets ToString for free
Key characteristics:
// From standard library
impl<T: Display> ToString for T { /* ... */ } // All Display types get ToString
impl<T> From<T> for T { /* ... */ } // Every type converts to itself
impl<T, U> Into<U> for T where U: From<T> { /* ... */ } // Into derived from From
impl<'a, T: ?Sized> From<&'a T> for *const T { /* ... */ } // References to raw pointers
Result for debugging and side-effects without modifying the standard library. Extension traits with blanket impls let you "extend" existing types.
/// Extension trait adding debugging utilities to Result types
trait ResultExt<T, E> {
/// Tap into Ok value for side effects without consuming it
fn tap_ok<F>(self, f: F) -> Self
where
F: FnOnce(&T);
/// Tap into Err value for side effects
fn tap_err<F>(self, f: F) -> Self
where
F: FnOnce(&E);
/// Log errors with context
fn log_err(self, context: &str) -> Self
where
E: std::fmt::Display;
}
// Blanket implementation for ALL Result types
impl<T, E> ResultExt<T, E> for Result<T, E> {
fn tap_ok<F>(self, f: F) -> Self
where
F: FnOnce(&T),
{
if let Ok(ref value) = self {
f(value);
}
self
}
fn tap_err<F>(self, f: F) -> Self
where
F: FnOnce(&E),
{
if let Err(ref err) = self {
f(err);
}
self
}
fn log_err(self, context: &str) -> Self
where
E: std::fmt::Display,
{
if let Err(ref err) = self {
eprintln!("[ERROR] {}: {}", context, err);
}
self
}
}
// Usage in systems programming - file operations with debugging
use std::fs::File;
use std::io::{self, Read, Write};
fn process_config_file(path: &str) -> io::Result<()> {
let mut contents = String::new();
File::open(path)
.tap_err(|e| eprintln!("Failed to open {}: {}", path, e))
.log_err("Config file access")
.and_then(|mut f| f.read_to_string(&mut contents))
.tap_ok(|_| println!("Read {} bytes", contents.len()))
.log_err("Config file read")?;
// Process contents...
Ok(())
}
// Extends to custom error types automatically
#[derive(Debug)]
enum DatabaseError {
ConnectionFailed,
QueryError(String),
}
impl std::fmt::Display for DatabaseError {
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
match self {
DatabaseError::ConnectionFailed => write!(f, "Database connection failed"),
DatabaseError::QueryError(q) => write!(f, "Query failed: {}", q),
}
}
}
fn query_database() -> Result<Vec<String>, DatabaseError> {
Err(DatabaseError::ConnectionFailed)
.tap_err(|_| eprintln!("Attempting reconnection..."))
.log_err("Database query")
}
Why blanket impl shines here:
Result with ANY T and E typesserde requires making container types (Vec, Option, etc.) serializable automatically if their contents are serializable.
use std::io::{self, Write};
/// Core serialization trait
trait Serialize {
fn serialize<W: Write>(&self, writer: &mut W) -> io::Result<()>;
}
// Primitive implementations
impl Serialize for i32 {
fn serialize<W: Write>(&self, writer: &mut W) -> io::Result<()> {
write!(writer, "{}", self)
}
}
impl Serialize for String {
fn serialize<W: Write>(&self, writer: &mut W) -> io::Result<()> {
write!(writer, "\"{}\"", self)
}
}
// Blanket impl: Vec<T> is serializable if T is serializable
impl<T: Serialize> Serialize for Vec<T> {
fn serialize<W: Write>(&self, writer: &mut W) -> io::Result<()> {
write!(writer, "[")?;
for (i, item) in self.iter().enumerate() {
if i > 0 {
write!(writer, ",")?;
}
item.serialize(writer)?;
}
write!(writer, "]")
}
}
// Blanket impl: Option<T> is serializable if T is serializable
impl<T: Serialize> Serialize for Option<T> {
fn serialize<W: Write>(&self, writer: &mut W) -> io::Result<()> {
match self {
Some(value) => value.serialize(writer),
None => write!(writer, "null"),
}
}
}
// Blanket impl: References to serializable types are serializable
impl<T: Serialize + ?Sized> Serialize for &T {
fn serialize<W: Write>(&self, writer: &mut W) -> io::Result<()> {
(*self).serialize(writer)
}
}
// Blanket impl: Box<T> is serializable if T is serializable
impl<T: Serialize + ?Sized> Serialize for Box<T> {
fn serialize<W: Write>(&self, writer: &mut W) -> io::Result<()> {
(**self).serialize(writer)
}
}
// Now complex nested structures work automatically!
#[derive(Debug)]
struct User {
id: i32,
name: String,
email: Option<String>,
}
impl Serialize for User {
fn serialize<W: Write>(&self, writer: &mut W) -> io::Result<()> {
write!(writer, "{{")?;
write!(writer, "\"id\":")?;
self.id.serialize(writer)?;
write!(writer, ",\"name\":")?;
self.name.serialize(writer)?;
write!(writer, ",\"email\":")?;
self.email.serialize(writer)?;
write!(writer, "}}")
}
}
fn serialize_data() -> io::Result<()> {
let users = vec![
User { id: 1, name: "Alice".to_string(), email: Some("alice@example.com".to_string()) },
User { id: 2, name: "Bob".to_string(), email: None },
];
let mut buffer = Vec::new();
// Vec<User> serializable because User is serializable!
users.serialize(&mut buffer)?;
// Option<Vec<User>> also works through composition!
let optional_users: Option<Vec<User>> = Some(users);
optional_users.serialize(&mut buffer)?;
// Box<Vec<Option<User>>> - arbitrarily deep nesting works!
let boxed: Box<Vec<Option<User>>> = Box::new(vec![Some(User {
id: 3,
name: "Charlie".to_string(),
email: Some("charlie@example.com".to_string()),
})]);
boxed.serialize(&mut buffer)?;
println!("{}", String::from_utf8_lossy(&buffer));
Ok(())
}
The power of composition:
// Each blanket impl builds on others:
// 1. User implements Serialize (manually)
// 2. Vec<User> gets Serialize from blanket impl
// 3. Option<User> gets Serialize from blanket impl
// 4. Vec<Option<User>> gets Serialize (Vec impl uses Option impl)
// 5. Box<Vec<Option<User>>> gets Serialize (Box impl uses Vec impl)
//
// Infinite combinations from finite implementations!
use std::future::Future;
use std::pin::Pin;
/// Trait for types that can be spawned on an async runtime
trait Spawnable {
type Output;
/// Spawn this task on the runtime
fn spawn(self) -> JoinHandle<Self::Output>;
}
/// Handle to a spawned task
struct JoinHandle<T> {
inner: Pin<Box<dyn Future<Output = T>>>,
}
impl<T> JoinHandle<T> {
fn new(fut: impl Future<Output = T> + 'static) -> Self {
Self {
inner: Box::pin(fut),
}
}
}
// Blanket impl: All async functions (FnOnce() -> Future) are spawnable
impl<F, Fut> Spawnable for F
where
F: FnOnce() -> Fut + Send + 'static,
Fut: Future + Send + 'static,
Fut::Output: Send + 'static,
{
type Output = Fut::Output;
fn spawn(self) -> JoinHandle<Self::Output> {
let fut = self();
// In real implementation, would call runtime's spawn
JoinHandle::new(async move {
fut.await
})
}
}
// Now write runtime-agnostic code
async fn fetch_data(url: String) -> Result<String, Box<dyn std::error::Error>> {
// Simulated async work
Ok(format!("Data from {}", url))
}
async fn process_urls() {
let urls = vec![
"https://api.example.com/users".to_string(),
"https://api.example.com/posts".to_string(),
];
// Spawn tasks using the Spawnable trait
let handles: Vec<_> = urls
.into_iter()
.map(|url| {
// Closure that returns a future - automatically Spawnable!
(move || async move {
fetch_data(url).await
}).spawn()
})
.collect();
// Wait for all tasks (simplified)
for handle in handles {
// In real code: handle.await
}
}
// More advanced: Blanket impl for async closures with arguments
trait SpawnWith<Args> {
type Output;
fn spawn_with(self, args: Args) -> JoinHandle<Self::Output>;
}
impl<F, Fut, Args> SpawnWith<Args> for F
where
F: FnOnce(Args) -> Fut + Send + 'static,
Fut: Future + Send + 'static,
Fut::Output: Send + 'static,
Args: Send + 'static,
{
type Output = Fut::Output;
fn spawn_with(self, args: Args) -> JoinHandle<Self::Output> {
JoinHandle::new(async move {
self(args).await
})
}
}
// Usage with arguments
async fn process_with_config() {
let config = vec!["setting1".to_string(), "setting2".to_string()];
let handle = (|cfg: Vec<String>| async move {
println!("Processing with config: {:?}", cfg);
cfg.len()
}).spawn_with(config);
}
Abstraction power:
use std::rc::Rc;
use std::sync::Arc;
// Conversion trait (simplified From/Into)
trait ConvertTo<T> {
fn convert_to(self) -> T;
}
// Blanket impl: Every type converts to itself (reflexive)
impl<T> ConvertTo<T> for T {
fn convert_to(self) -> T {
self
}
}
// Box<T> converts to Arc<T>
impl<T> ConvertTo<Arc<T>> for Box<T> {
fn convert_to(self) -> Arc<T> {
Arc::from(self)
}
}
// Box<T> converts to Rc<T>
impl<T> ConvertTo<Rc<T>> for Box<T> {
fn convert_to(self) -> Rc<T> {
Rc::from(self)
}
}
// Vec<T> converts to Box<[T]>
impl<T> ConvertTo<Box<[T]>> for Vec<T> {
fn convert_to(self) -> Box<[T]> {
self.into_boxed_slice()
}
}
// String converts to Box<str>
impl ConvertTo<Box<str>> for String {
fn convert_to(self) -> Box<str> {
self.into_boxed_str()
}
}
// Blanket impl: If A converts to B, then Option<A> converts to Option<B>
impl<T, U> ConvertTo<Option<U>> for Option<T>
where
T: ConvertTo<U>,
{
fn convert_to(self) -> Option<U> {
self.map(|t| t.convert_to())
}
}
// Blanket impl: If A converts to B, then Vec<A> converts to Vec<B>
impl<T, U> ConvertTo<Vec<U>> for Vec<T>
where
T: ConvertTo<U>,
{
fn convert_to(self) -> Vec<U> {
self.into_iter().map(|t| t.convert_to()).collect()
}
}
// Real-world usage in memory management
struct Resource {
id: u64,
data: Vec<u8>,
}
fn process_resource() {
// Start with Box (single ownership)
let resource = Box::new(Resource {
id: 42,
data: vec![1, 2, 3, 4],
});
// Need to share across threads? Convert to Arc
let shared: Arc<Resource> = resource.convert_to();
// Clone the Arc for multiple threads
let shared1 = shared.clone();
let shared2 = shared.clone();
// Spawn threads (simplified - would use real threading)
// thread::spawn(move || { /* use shared1 */ });
// thread::spawn(move || { /* use shared2 */ });
}
// Composable conversions
fn composable_example() {
// Vec<Box<i32>> -> Vec<Arc<i32>> works automatically!
let boxed_numbers: Vec<Box<i32>> = vec![Box::new(1), Box::new(2), Box::new(3)];
let arc_numbers: Vec<Arc<i32>> = boxed_numbers.convert_to();
// Option<Vec<Box<String>>> -> Option<Vec<Box<str>>> composable conversion!
let complex: Option<Vec<Box<String>>> = Some(vec![
Box::new("hello".to_string()),
Box::new("world".to_string()),
]);
// Each layer uses a different blanket impl!
// 1. Option<Vec<Box<String>>> uses the Option blanket impl
// 2. Which uses Vec<Box<String>> -> Vec<Box<str>> blanket impl
// 3. Which uses Box<String> -> Box<str> specific impl
let converted: Option<Vec<Box<str>>> = complex.convert_to();
}
Memory management benefits:
Iterator trait is stable, but you want to add your own convenience methods without forking std. Extension traits with blanket impls are the solution.
/// Extension trait adding utility methods to all iterators
trait IteratorExt: Iterator {
/// Collect into a Vec (shorthand for .collect::<Vec<_>>())
fn collect_vec(self) -> Vec<Self::Item>
where
Self: Sized,
{
self.collect()
}
/// Try to collect, returning None if any element is None
fn try_collect_vec<T>(self) -> Option<Vec<T>>
where
Self: Sized + Iterator<Item = Option<T>>,
{
self.collect()
}
/// Collect into a Result, short-circuiting on first error
fn try_collect_result<T, E>(self) -> Result<Vec<T>, E>
where
Self: Sized + Iterator<Item = Result<T, E>>,
{
self.collect()
}
/// Count elements satisfying a predicate
fn count_where<F>(self, predicate: F) -> usize
where
Self: Sized,
F: FnMut(&Self::Item) -> bool,
{
self.filter(predicate).count()
}
/// Split into two vecs based on predicate (like partition but returns Vecs)
fn partition_vec<F>(self, mut predicate: F) -> (Vec<Self::Item>, Vec<Self::Item>)
where
Self: Sized,
F: FnMut(&Self::Item) -> bool,
{
let mut left = Vec::new();
let mut right = Vec::new();
for item in self {
if predicate(&item) {
left.push(item);
} else {
right.push(item);
}
}
(left, right)
}
/// Apply a fallible operation, collecting only successes and logging errors
fn filter_map_result<T, E, F>(self, mut f: F) -> Vec<T>
where
Self: Sized,
F: FnMut(Self::Item) -> Result<T, E>,
E: std::fmt::Display,
{
self.filter_map(|item| {
match f(item) {
Ok(value) => Some(value),
Err(e) => {
eprintln!("Error processing item: {}", e);
None
}
}
})
.collect()
}
}
// THE KEY: Blanket implementation for ALL iterators
impl<I: Iterator> IteratorExt for I {}
// Now every iterator in your codebase gets these methods!
fn demonstrate_extension_methods() {
// collect_vec() shorthand
let numbers: Vec<i32> = (1..10).collect_vec();
// count_where() predicate counting
let even_count = numbers.iter().count_where(|&&n| n % 2 == 0);
println!("Even numbers: {}", even_count);
// partition_vec() for easy splitting
let (evens, odds) = numbers.into_iter().partition_vec(|&n| n % 2 == 0);
println!("Evens: {:?}, Odds: {:?}", evens, odds);
// try_collect_result() for error handling
let results = vec![Ok(1), Ok(2), Err("failed"), Ok(4)];
match results.into_iter().try_collect_result::<i32, &str>() {
Ok(values) => println!("All succeeded: {:?}", values),
Err(e) => println!("Failed with: {}", e),
}
// filter_map_result() for partial success
let strings = vec!["42", "not a number", "100", "invalid"];
let parsed: Vec<i32> = strings
.into_iter()
.filter_map_result(|s| s.parse::<i32>().map_err(|e| e.to_string()));
println!("Parsed: {:?}", parsed);
}
// Works with ANY iterator - even custom ones!
struct FibonacciIterator {
curr: u64,
next: u64,
}
impl Iterator for FibonacciIterator {
type Item = u64;
fn next(&mut self) -> Option<Self::Item> {
let current = self.curr;
self.curr = self.next;
self.next = current + self.next;
Some(current)
}
}
fn fibonacci_with_extension_methods() {
let fib = FibonacciIterator { curr: 0, next: 1 };
// Extension methods work on custom iterators automatically!
let first_ten: Vec<u64> = fib.take(10).collect_vec();
println!("First 10 Fibonacci: {:?}", first_ten);
let fib2 = FibonacciIterator { curr: 0, next: 1 };
let (even_fib, odd_fib) = fib2
.take(20)
.partition_vec(|&n| n % 2 == 0);
println!("Even Fibonacci: {:?}", even_fib);
}
Extension trait power:
impl IteratorExt for I applies to all iteratorsBlanket implementations don't create a single piece of code that runs for all types. Instead, the compiler generates specialized versions for each concrete type you use:
trait MyTrait {
fn process(&self);
}
// Blanket impl for all types with Display
impl<T: Display> MyTrait for T {
fn process(&self) {
println!("Processing: {}", self);
}
}
fn use_blanket_impl() {
let num = 42_i32;
num.process(); // Generates specialized version for i32
let text = "hello";
text.process(); // Generates specialized version for &str
let pi = 3.14_f64;
pi.process(); // Generates specialized version for f64
}
// Compiler generates approximately:
// impl MyTrait for i32 {
// fn process(&self) {
// println!("Processing: {}", self); // Calls Display for i32
// }
// }
//
// impl MyTrait for &str {
// fn process(&self) {
// println!("Processing: {}", self); // Calls Display for &str
// }
// }
//
// impl MyTrait for f64 {
// fn process(&self) {
// println!("Processing: {}", self); // Calls Display for f64
// }
// }
Monomorphization characteristics:
Rust's coherence rules ensure there's never ambiguity about which trait impl to use. The orphan rule states: you can only implement a trait for a type if either the trait or the type is defined in your crate.
// Example of coherence working with blanket impls
trait MyTrait { fn my_method(&self); }
// Blanket impl in your crate
impl<T: Display> MyTrait for T {
fn my_method(&self) {
println!("{}", self);
}
}
// This would CONFLICT - can't have two impls that might overlap:
// impl MyTrait for String { // ERROR! Conflicts with blanket impl
// fn my_method(&self) {
// println!("Specific String impl");
// }
// }
// Coherence error:
// conflicting implementations of trait `MyTrait` for type `String`
// blanket impl already provides MyTrait for String (via Display bound)
Coherence ensures:
The limitation shown above - can't provide a specific impl when a blanket impl exists - is addressed by specialization (RFC 1210), currently unstable:
// Requires nightly Rust
#![feature(specialization)]
trait Cost {
fn cost(&self) -> usize;
}
// Default blanket impl - high cost
impl<T> Cost for T {
default fn cost(&self) -> usize {
100 // Generic expensive operation
}
}
// Specialized impl for Copy types - low cost
impl<T: Copy> Cost for T {
fn cost(&self) -> usize {
1 // Cheap to copy
}
}
// Even more specialized for i32
impl Cost for i32 {
fn cost(&self) -> usize {
0 // Trivial cost
}
}
fn check_costs() {
struct BigStruct([u8; 1000]);
let big = BigStruct([0; 1000]);
println!("BigStruct cost: {}", big.cost()); // 100 (default)
let num = 42_u64;
println!("u64 cost: {}", num.cost()); // 1 (Copy specialization)
let small = 42_i32;
println!("i32 cost: {}", small.cost()); // 0 (most specific)
}
Specialization allows:
When designing traits with blanket impls, you must avoid overlapping implementations:
trait Process { fn process(&self); }
// These two blanket impls would CONFLICT:
// impl<T: Display> Process for T { /* ... */ }
// impl<T: Debug> Process for T { /* ... */ }
//
// ERROR: Many types implement both Display and Debug!
// For String, which impl should Rust use?
// SOLUTION 1: Make impls non-overlapping using negative bounds (unstable)
#![feature(negative_impls)]
impl<T: Display> Process for T { /* ... */ }
impl<T: Debug + !Display> Process for T { /* ... */ } // Only Debug, not Display
// SOLUTION 2: Use marker traits to disambiguate
trait DisplayMarker {}
trait DebugMarker {}
impl<T: Display + DisplayMarker> Process for T { /* ... */ }
impl<T: Debug + DebugMarker> Process for T { /* ... */ }
// Users opt in to which impl they want:
impl DisplayMarker for MyType {}
// SOLUTION 3: Different traits (recommended)
trait ProcessDisplay { fn process_display(&self); }
trait ProcessDebug { fn process_debug(&self); }
impl<T: Display> ProcessDisplay for T { /* ... */ }
impl<T: Debug> ProcessDebug for T { /* ... */ }
Strategies to avoid conflicts:
When you need a specific implementation but a blanket impl blocks you, use the newtype pattern:
trait Summarize {
fn summary(&self) -> String;
}
// Blanket impl
impl<T: Display> Summarize for T {
fn summary(&self) -> String {
format!("Value: {}", self)
}
}
// Want different behavior for String? Use newtype!
struct MyString(String);
impl Summarize for MyString {
fn summary(&self) -> String {
format!("MyString({} chars): {}", self.0.len(), self.0)
}
}
fn compare_impls() {
let regular = String::from("hello");
println!("{}", regular.summary()); // "Value: hello" (blanket impl)
let wrapped = MyString(String::from("hello"));
println!("{}", wrapped.summary()); // "MyString(5 chars): hello" (specific impl)
}
// Add methods to existing types you don't own
trait ResultExt<T, E> { /* methods */ }
impl<T, E> ResultExt<T, E> for Result<T, E> { /* ... */ }
trait IteratorExt: Iterator { /* methods */ }
impl<I: Iterator> IteratorExt for I { /* ... */ }
2. Automatic Trait Derivation
// Derive one trait from another
trait Into<T> { /* ... */ }
impl<T, U> Into<U> for T
where U: From<T>
{ /* automatically implemented */ }
3. Generic Conversions
// Conversions that apply to families of types
impl<T> From<T> for Option<T> { /* ... */ }
impl<T> From<T> for Box<T> { /* ... */ }
4. Composable Abstractions
// Build complex behavior from simple building blocks
impl<T: Serialize> Serialize for Vec<T> { /* ... */ }
impl<T: Serialize> Serialize for Option<T> { /* ... */ }
// Now Vec<Option<T>> automatically serializable!
5. Wrapper Trait Implementations
// Implement for all wrapper types at once
impl<T: Display + ?Sized> Display for Box<T> {
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
(**self).fmt(f)
}
}
impl<T: Display + ?Sized> Display for Arc<T> { /* same pattern */ }
impl<T: Display + ?Sized> Display for Rc<T> { /* same pattern */ }
// BAD: Too broad, blocks future implementations
trait MyTrait { fn method(&self); }
// This prevents ANY specific impl in your crate or downstream
impl<T> MyTrait for T { // Too generic!
fn method(&self) { /* default */ }
}
// BETTER: Restrict with meaningful bounds
impl<T: Display + Debug> MyTrait for T {
fn method(&self) { /* ... */ }
}
2. Conflicting Blanket Impls
// BAD: These overlap for many types
trait Process { fn process(&self); }
impl<T: Clone> Process for T { /* ... */ } // Many types are Clone
impl<T: Copy> Process for T { /* ... */ } // All Copy types are Clone!
// Conflict for any Copy type!
// BETTER: Non-overlapping bounds or separate traits
trait ProcessClone { fn process(&self); }
trait ProcessCopy { fn process(&self); }
impl<T: Clone> ProcessClone for T { /* ... */ }
impl<T: Copy> ProcessCopy for T { /* ... */ }
3. Not Considering Downstream Users
// In your library:
pub trait Config { /* ... */ }
// BAD: Blanket impl prevents users from implementing Config
impl<T: Serialize> Config for T { /* ... */ }
// User's code (trying to implement Config):
// struct MyConfig { /* ... */ }
// impl Config for MyConfig { /* ... */ } // ERROR if MyConfig: Serialize!
// BETTER: Don't use blanket impl for traits intended for user implementation
// Or document clearly that types must not implement Serialize
4. Performance Traps with Binary Size
// BAD: Complex generic function with blanket impl
pub trait Process { fn process(&self) -> Vec<String>; }
impl<T: Debug + Clone + Display> Process for T {
fn process(&self) -> Vec<String> {
// 100 lines of complex logic
vec![format!("{:?}", self), format!("{}", self)]
}
}
// Problem: This generates code for EVERY type used with Process
// If used with 50 types, get 50 copies of that 100-line function
// Binary size bloat!
// BETTER: Extract common logic to non-generic helper functions
impl<T: Debug + Clone + Display> Process for T {
fn process(&self) -> Vec<String> {
process_impl(&format!("{:?}", self), &format!("{}", self))
}
}
// Non-generic helper - only one copy in binary
fn process_impl(debug: &str, display: &str) -> Vec<String> {
// 100 lines of logic here
vec![debug.to_string(), display.to_string()]
}
5. Forgetting Coherence Rules
// In your crate, you define:
trait MyTrait { /* ... */ }
// BAD: Blanket impl for foreign types with foreign trait bound
impl<T: std::fmt::Display> MyTrait for Vec<T> { // OK currently
// ...
}
// Problem: If std later adds `impl Display for SomeNewType`
// your blanket impl now applies to Vec<SomeNewType>
// This could cause unexpected behavior or conflicts
// BETTER: Implement for specific types you control
impl MyTrait for Vec<MyType> { /* ... */ }
// OR: Use marker traits to make intent explicit
trait MyMarker {}
impl<T: Display + MyMarker> MyTrait for Vec<T> { /* ... */ }
trait Transform {
fn transform(&self) -> String;
}
impl<T: Display> Transform for T {
fn transform(&self) -> String {
format!("Transformed: {}", self)
}
}
fn use_transform() {
let a = 42_i32;
let b = 3.14_f64;
let c = "hello";
a.transform(); // Generates Transform impl for i32
b.transform(); // Generates Transform impl for f64
c.transform(); // Generates Transform impl for &str
}
// Compiler generates approximately this code:
//
// impl Transform for i32 {
// #[inline]
// fn transform(&self) -> String {
// format!("Transformed: {}", self)
// }
// }
//
// impl Transform for f64 {
// #[inline]
// fn transform(&self) -> String {
// format!("Transformed: {}", self)
// }
// }
//
// impl Transform for &str {
// #[inline]
// fn transform(&self) -> String {
// format!("Transformed: {}", self)
// }
// }
Monomorphization benefits:
// Example: Large blanket impl
pub trait JsonSerialize {
fn to_json(&self) -> String;
}
impl<T: Debug + Display + Clone> JsonSerialize for T {
fn to_json(&self) -> String {
// 200 lines of complex JSON formatting logic
format!("{{\"debug\": \"{:?}\", \"display\": \"{}\"}}", self, self)
}
}
// If used with 100 different types:
// - 100 copies of that 200-line function in binary
// - Could be 100KB+ of duplicated code
//
// Mitigation: Extract to non-generic helpers
impl<T: Debug + Display + Clone> JsonSerialize for T {
fn to_json(&self) -> String {
json_serialize_impl(&format!("{:?}", self), &format!("{}", self))
}
}
// Only ONE copy of this in the binary
fn json_serialize_impl(debug_str: &str, display_str: &str) -> String {
// 200 lines of logic here - generated once
format!("{{\"debug\": \"{}\", \"display\": \"{}\"}}", debug_str, display_str)
}
Binary size optimization strategies:
Blanket implementations have identical runtime performance to hand-written implementations:
// Blanket impl version
trait Show { fn show(&self); }
impl<T: Display> Show for T {
#[inline]
fn show(&self) {
println!("{}", self);
}
}
// Manual impl version
trait Show2 { fn show2(&self); }
impl Show2 for i32 {
#[inline]
fn show2(&self) {
println!("{}", self);
}
}
// These compile to IDENTICAL machine code:
fn compare_performance() {
let x = 42_i32;
x.show(); // Blanket impl
x.show2(); // Manual impl
// Both inline to same assembly instructions
}
Performance characteristics:
Vec.
// TODO: Implement this trait
trait VecExt<T> {
/// Returns true if the vector contains duplicates
fn has_duplicates(&self) -> bool
where
T: PartialEq;
/// Returns a new Vec with duplicates removed (preserving order)
fn dedup_by_order(self) -> Vec<T>
where
T: PartialEq;
/// Splits the vec into chunks of size n (last chunk may be smaller)
fn chunks_exact_vec(&self, n: usize) -> Vec<Vec<T>>
where
T: Clone;
}
// TODO: Implement blanket impl for Vec<T>
// impl<T> VecExt<T> for Vec<T> {
// // Your implementation here
// }
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_has_duplicates() {
assert!(vec![1, 2, 3, 2].has_duplicates());
assert!(!vec![1, 2, 3, 4].has_duplicates());
}
#[test]
fn test_dedup_by_order() {
let v = vec![1, 2, 3, 2, 4, 1];
assert_eq!(v.dedup_by_order(), vec![1, 2, 3, 4]);
}
#[test]
fn test_chunks_exact_vec() {
let v = vec![1, 2, 3, 4, 5];
assert_eq!(v.chunks_exact_vec(2), vec![vec![1, 2], vec![3, 4], vec![5]]);
}
}
Hint: For has_duplicates, you can use nested iteration or convert to a set and compare lengths.
use std::fmt::Display;
/// Result of validation
#[derive(Debug, PartialEq)]
pub enum ValidationResult {
Valid,
Invalid(Vec<String>),
}
impl ValidationResult {
fn combine(self, other: ValidationResult) -> ValidationResult {
match (self, other) {
(ValidationResult::Valid, ValidationResult::Valid) => ValidationResult::Valid,
(ValidationResult::Invalid(mut e1), ValidationResult::Invalid(e2)) => {
e1.extend(e2);
ValidationResult::Invalid(e1)
}
(ValidationResult::Invalid(e), _) | (_, ValidationResult::Invalid(e)) => {
ValidationResult::Invalid(e)
}
}
}
}
/// Core validation trait
pub trait Validate {
fn validate(&self) -> ValidationResult;
}
// TODO: Implement blanket impl for Option<T> where T: Validate
// Should validate the inner value if Some, return Valid if None
// impl<T: Validate> Validate for Option<T> { /* ... */ }
// TODO: Implement blanket impl for Vec<T> where T: Validate
// Should validate all elements and combine results
// impl<T: Validate> Validate for Vec<T> { /* ... */ }
// TODO: Implement blanket impl for (T, U) where T: Validate, U: Validate
// Should validate both elements and combine results
// impl<T: Validate, U: Validate> Validate for (T, U) { /* ... */ }
// Example validator implementations
struct Email(String);
impl Validate for Email {
fn validate(&self) -> ValidationResult {
if self.0.contains('@') {
ValidationResult::Valid
} else {
ValidationResult::Invalid(vec![format!("Invalid email: {}", self.0)])
}
}
}
struct Age(u32);
impl Validate for Age {
fn validate(&self) -> ValidationResult {
if self.0 >= 18 && self.0 <= 120 {
ValidationResult::Valid
} else {
ValidationResult::Invalid(vec![format!("Invalid age: {}", self.0)])
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_option_validation() {
let some_valid = Some(Email("test@example.com".to_string()));
assert_eq!(some_valid.validate(), ValidationResult::Valid);
let some_invalid = Some(Email("invalid".to_string()));
assert!(matches!(some_invalid.validate(), ValidationResult::Invalid(_)));
let none: Option<Email> = None;
assert_eq!(none.validate(), ValidationResult::Valid);
}
#[test]
fn test_vec_validation() {
let emails = vec![
Email("user1@example.com".to_string()),
Email("user2@example.com".to_string()),
];
assert_eq!(emails.validate(), ValidationResult::Valid);
let mixed = vec![
Email("valid@example.com".to_string()),
Email("invalid".to_string()),
];
assert!(matches!(mixed.validate(), ValidationResult::Invalid(_)));
}
#[test]
fn test_tuple_validation() {
let valid = (Email("test@example.com".to_string()), Age(25));
assert_eq!(valid.validate(), ValidationResult::Valid);
let invalid = (Email("invalid".to_string()), Age(200));
if let ValidationResult::Invalid(errors) = invalid.validate() {
assert_eq!(errors.len(), 2);
} else {
panic!("Expected Invalid result");
}
}
#[test]
fn test_nested_validation() {
// Vec<Option<Email>> should work through blanket impls!
let nested = vec![
Some(Email("user1@example.com".to_string())),
None,
Some(Email("invalid".to_string())),
];
assert!(matches!(nested.validate(), ValidationResult::Invalid(_)));
}
}
Hint: Each blanket impl should iterate through its structure, call .validate() on each element, and combine results using ValidationResult::combine().
use std::future::Future;
use std::pin::Pin;
use std::task::{Context, Poll};
/// A handle to a spawned task
pub struct JoinHandle<T> {
future: Pin<Box<dyn Future<Output = T> + Send>>,
}
impl<T> Future for JoinHandle<T> {
type Output = T;
fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
self.future.as_mut().poll(cx)
}
}
/// Trait for types that can be spawned on the executor
pub trait Spawn {
type Output;
fn spawn(self) -> JoinHandle<Self::Output>;
}
// TODO: Implement blanket impl for FnOnce() -> Future
// impl<F, Fut> Spawn for F
// where
// F: FnOnce() -> Fut + Send + 'static,
// Fut: Future + Send + 'static,
// Fut::Output: Send + 'static,
// { /* ... */ }
/// Trait for spawning with one argument
pub trait SpawnWith<A> {
type Output;
fn spawn_with(self, arg: A) -> JoinHandle<Self::Output>;
}
// TODO: Implement blanket impl for FnOnce(A) -> Future
// impl<F, Fut, A> SpawnWith<A> for F
// where
// F: FnOnce(A) -> Fut + Send + 'static,
// Fut: Future + Send + 'static,
// Fut::Output: Send + 'static,
// A: Send + 'static,
// { /* ... */ }
/// Trait for spawning with two arguments
pub trait SpawnWith2<A, B> {
type Output;
fn spawn_with2(self, arg1: A, arg2: B) -> JoinHandle<Self::Output>;
}
// TODO: Implement blanket impl for FnOnce(A, B) -> Future
// impl<F, Fut, A, B> SpawnWith2<A, B> for F
// where
// F: FnOnce(A, B) -> Fut + Send + 'static,
// Fut: Future + Send + 'static,
// Fut::Output: Send + 'static,
// A: Send + 'static,
// B: Send + 'static,
// { /* ... */ }
#[cfg(test)]
mod tests {
use super::*;
#[tokio::test]
async fn test_spawn_no_args() {
let handle = (|| async {
42
}).spawn();
let result = handle.await;
assert_eq!(result, 42);
}
#[tokio::test]
async fn test_spawn_with_one_arg() {
let handle = (|x: i32| async move {
x * 2
}).spawn_with(21);
let result = handle.await;
assert_eq!(result, 42);
}
#[tokio::test]
async fn test_spawn_with_two_args() {
let handle = (|x: i32, y: i32| async move {
x + y
}).spawn_with2(40, 2);
let result = handle.await;
assert_eq!(result, 42);
}
#[tokio::test]
async fn test_multiple_spawns() {
let handles: Vec<_> = (0..10)
.map(|i| {
(move || async move {
i * i
}).spawn()
})
.collect();
let results: Vec<_> = futures::future::join_all(handles).await;
assert_eq!(results, vec![0, 1, 4, 9, 16, 25, 36, 49, 64, 81]);
}
}
Hint: For the implementation, you'll need to:
The most ubiquitous example of blanket impls in Rust:
// From std::convert
// Reflexive: every type converts to itself
impl<T> From<T> for T {
fn from(t: T) -> T { t }
}
// Blanket impl: Into is automatically derived from From
impl<T, U> Into<U> for T
where
U: From<T>,
{
fn into(self) -> U {
U::from(self)
}
}
// This means: implement From, get Into for free!
struct UserId(u64);
impl From<u64> for UserId {
fn from(id: u64) -> Self {
UserId(id)
}
}
// Into<UserId> automatically available for u64:
let user_id: UserId = 42u64.into(); // Works via blanket impl!
// From std::string
pub trait ToString {
fn to_string(&self) -> String;
}
// Blanket impl: all Display types get ToString
impl<T: Display + ?Sized> ToString for T {
fn to_string(&self) -> String {
use std::fmt::Write;
let mut buf = String::new();
buf.write_fmt(format_args!("{}", self))
.expect("a Display implementation returned an error unexpectedly");
buf
}
}
// Result: every type with Display automatically has .to_string()
let num = 42;
let s = num.to_string(); // Works! i32 implements Display
// Simplified from serde
pub trait Serialize {
fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error>;
}
// Blanket impl for references
impl<T: Serialize + ?Sized> Serialize for &T {
fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
(*self).serialize(serializer)
}
}
// Blanket impl for Box
impl<T: Serialize + ?Sized> Serialize for Box<T> {
fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
(**self).serialize(serializer)
}
}
// Blanket impl for Vec
impl<T: Serialize> Serialize for Vec<T> {
fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
serializer.collect_seq(self)
}
}
// Blanket impl for Option
impl<T: Serialize> Serialize for Option<T> {
fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
match *self {
Some(ref value) => serializer.serialize_some(value),
None => serializer.serialize_none(),
}
}
}
// These compose: Vec<Option<Box<MyType>>> automatically serializable!
// Simplified from futures crate
pub trait StreamExt: Stream {
// Extension methods...
fn next(&mut self) -> Next<'_, Self>
where
Self: Unpin;
fn collect<C: Default + Extend<Self::Item>>(self) -> Collect<Self, C>
where
Self: Sized;
fn filter<Fut, F>(self, f: F) -> Filter<Self, Fut, F>
where
F: FnMut(&Self::Item) -> Fut,
Fut: Future<Output = bool>,
Self: Sized;
}
// THE MAGIC: One blanket impl gives all streams these methods
impl<T: Stream + ?Sized> StreamExt for T {}
// Now every Stream in your codebase gets these extension methods
use futures::stream::{self, StreamExt};
async fn use_stream_ext() {
let items = stream::iter(vec![1, 2, 3, 4, 5])
.filter(|x| async move { x % 2 == 0 })
.collect::<Vec<_>>()
.await;
assert_eq!(items, vec![2, 4]);
}
// Simplified from itertools crate
pub trait Itertools: Iterator {
fn collect_vec(self) -> Vec<Self::Item>
where
Self: Sized,
{
self.collect()
}
fn unique(self) -> Unique<Self>
where
Self: Sized,
Self::Item: Eq + Hash;
fn counts(self) -> HashMap<Self::Item, usize>
where
Self: Sized,
Self::Item: Eq + Hash;
}
// Blanket impl for all iterators
impl<T: Iterator + ?Sized> Itertools for T {}
// Usage: every iterator gets these methods
use itertools::Itertools;
fn example() {
let v = vec![1, 2, 2, 3, 3, 3];
// .unique() from extension trait
let unique: Vec<_> = v.iter().unique().collect();
// .counts() from extension trait
let counts = v.iter().counts();
// counts = {1: 1, 2: 2, 3: 3}
}
---
Pattern Completed: Blanket Implementations Key Takeaways:Run this code in the official Rust Playground