381: Blanket Implementations
Tutorial Video
Text description (accessibility)
This video demonstrates the "381: Blanket Implementations" functional Rust example. Difficulty level: Advanced. Key concepts covered: Functional Programming. When a trait's functionality can be derived entirely from another trait, duplicating the implementation for every concrete type is tedious and error-prone. Key difference from OCaml: 1. **Automatic vs. explicit**: Rust's blanket impls apply automatically when bounds are satisfied; OCaml functors must be applied explicitly per type.
Tutorial
The Problem
When a trait's functionality can be derived entirely from another trait, duplicating the implementation for every concrete type is tedious and error-prone. Blanket implementations solve this by implementing a trait for all types satisfying a bound: impl<T: Bound> MyTrait for T. This is how the standard library implements ToString for everything that implements Display, and how From blanket-implies Into. The technique enables powerful composable abstractions without requiring each type to opt in explicitly.
Blanket impls are foundational to Rust's trait system and appear throughout std: From/Into conversions, Iterator adapters, AsRef/AsMut, and the serde serialization framework's generic implementations.
🎯 Learning Outcomes
impl<T: Display> Summary for T applies to all Display types simultaneouslyCode Example
#![allow(clippy::all)]
//! Blanket Implementations
//!
//! Implement a trait for all types that satisfy a bound.
use std::fmt;
/// Trait with summarization capability
pub trait Summary {
fn summarize(&self) -> String;
}
/// Blanket impl: anything that is Display also gets Summary
impl<T: fmt::Display> Summary for T {
fn summarize(&self) -> String {
format!("Summary: {}", self)
}
}
/// Another example: double the string representation
pub trait DoubleString {
fn double_string(&self) -> String;
}
impl<T: fmt::Display> DoubleString for T {
fn double_string(&self) -> String {
let s = self.to_string();
format!("{}{}", s, s)
}
}
/// Blanket impl for Into
pub trait IntoJson {
fn into_json(&self) -> String;
}
impl<T: fmt::Debug> IntoJson for T {
fn into_json(&self) -> String {
format!("{{\"{:?}\"}}", self)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_blanket_summary() {
assert_eq!(42i32.summarize(), "Summary: 42");
assert_eq!("hi".summarize(), "Summary: hi");
}
#[test]
fn test_double_string() {
assert_eq!(7u32.double_string(), "77");
assert_eq!("abc".double_string(), "abcabc");
}
#[test]
fn test_float_summary() {
assert_eq!(3.14f64.summarize(), "Summary: 3.14");
}
#[test]
fn test_into_json() {
let val = vec![1, 2, 3];
let json = val.into_json();
assert!(json.contains("[1, 2, 3]"));
}
#[test]
fn test_blanket_for_option() {
let opt = Some(42);
// Option<T> where T: Display implements Display
// so our blanket impl applies
assert!(opt.into_json().contains("Some"));
}
}Key Differences
OCaml Approach
OCaml achieves similar effects through module functors: module MakeSummary (M : Display) = struct let summarize x = "Summary: " ^ M.to_string x end. This requires explicit functor application (module IntSummary = MakeSummary(Int)), unlike Rust's automatic blanket resolution. OCaml's type classes (via first-class modules or modular implicits) provide similar power but require more explicit wiring.
Full Source
#![allow(clippy::all)]
//! Blanket Implementations
//!
//! Implement a trait for all types that satisfy a bound.
use std::fmt;
/// Trait with summarization capability
pub trait Summary {
fn summarize(&self) -> String;
}
/// Blanket impl: anything that is Display also gets Summary
impl<T: fmt::Display> Summary for T {
fn summarize(&self) -> String {
format!("Summary: {}", self)
}
}
/// Another example: double the string representation
pub trait DoubleString {
fn double_string(&self) -> String;
}
impl<T: fmt::Display> DoubleString for T {
fn double_string(&self) -> String {
let s = self.to_string();
format!("{}{}", s, s)
}
}
/// Blanket impl for Into
pub trait IntoJson {
fn into_json(&self) -> String;
}
impl<T: fmt::Debug> IntoJson for T {
fn into_json(&self) -> String {
format!("{{\"{:?}\"}}", self)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_blanket_summary() {
assert_eq!(42i32.summarize(), "Summary: 42");
assert_eq!("hi".summarize(), "Summary: hi");
}
#[test]
fn test_double_string() {
assert_eq!(7u32.double_string(), "77");
assert_eq!("abc".double_string(), "abcabc");
}
#[test]
fn test_float_summary() {
assert_eq!(3.14f64.summarize(), "Summary: 3.14");
}
#[test]
fn test_into_json() {
let val = vec![1, 2, 3];
let json = val.into_json();
assert!(json.contains("[1, 2, 3]"));
}
#[test]
fn test_blanket_for_option() {
let opt = Some(42);
// Option<T> where T: Display implements Display
// so our blanket impl applies
assert!(opt.into_json().contains("Some"));
}
}#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_blanket_summary() {
assert_eq!(42i32.summarize(), "Summary: 42");
assert_eq!("hi".summarize(), "Summary: hi");
}
#[test]
fn test_double_string() {
assert_eq!(7u32.double_string(), "77");
assert_eq!("abc".double_string(), "abcabc");
}
#[test]
fn test_float_summary() {
assert_eq!(3.14f64.summarize(), "Summary: 3.14");
}
#[test]
fn test_into_json() {
let val = vec![1, 2, 3];
let json = val.into_json();
assert!(json.contains("[1, 2, 3]"));
}
#[test]
fn test_blanket_for_option() {
let opt = Some(42);
// Option<T> where T: Display implements Display
// so our blanket impl applies
assert!(opt.into_json().contains("Some"));
}
}
Deep Comparison
OCaml vs Rust: Blanket Impls
Rust: impl<T: Bound> Trait for T. OCaml: module functors.
Exercises
Printable trait with a print(&self) method and implement it as a blanket impl for all Display types. Verify it works for i32, f64, String, and a custom struct.Validate trait with fn validate(&self) -> Result<(), String>. Create a NonEmpty marker trait, then write a blanket impl of Validate for all types implementing NonEmpty + Display.T: Display and one for T: Debug) and document the compiler error message that results, explaining why coherence requires one impl to win.