ExamplesBy LevelBy TopicLearning Paths
381 Advanced

381: Blanket Implementations

Functional Programming

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

  • • Understand how blanket implementations reduce boilerplate across entire type families
  • • Learn the coherence rules that prevent conflicting blanket implementations
  • • See how impl<T: Display> Summary for T applies to all Display types simultaneously
  • • Understand the orphan rule constraint: either the trait or the type must be local to your crate
  • • Learn when blanket impls are appropriate vs. when they create conflicts
  • Code 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

  • Automatic vs. explicit: Rust's blanket impls apply automatically when bounds are satisfied; OCaml functors must be applied explicitly per type.
  • Coherence: Rust enforces that no two blanket impls can conflict (overlapping implementations are rejected); OCaml has no global coherence requirement.
  • Orphan rule: Rust prevents implementing a foreign trait for a foreign type; OCaml modules have no orphan restrictions since implementations are local to modules.
  • Discoverability: Rust's blanket impls are visible in documentation (rustdoc shows "implementors"); OCaml functor applications are invisible unless explicitly named.
  • 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"));
        }
    }
    ✓ Tests Rust test suite
    #[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 blanket: Define a 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 blanket: Define a 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.
  • Conflict demonstration: Write two blanket impls that would conflict (e.g., one for T: Display and one for T: Debug) and document the compiler error message that results, explaining why coherence requires one impl to win.
  • Open Source Repos