ExamplesBy LevelBy TopicLearning Paths
399 Advanced

399: Coherence and Orphan Rules

Functional Programming

Tutorial Video

Text description (accessibility)

This video demonstrates the "399: Coherence and Orphan Rules" functional Rust example. Difficulty level: Advanced. Key concepts covered: Functional Programming. In a large ecosystem with thousands of crates, two independent crates could both implement `Display` for `Vec<i32>`, creating an ambiguous implementation conflict. Key difference from OCaml: 1. **Coherence guarantee**: Rust has global coherence — one impl per `(trait, type)` pair; OCaml relies on lexical scoping and `open` ordering.

Tutorial

The Problem

In a large ecosystem with thousands of crates, two independent crates could both implement Display for Vec<i32>, creating an ambiguous implementation conflict. Rust's orphan rule prevents this: you can only implement a trait for a type if either the trait or the type is defined in your current crate. This coherence guarantee ensures that every (trait, type) pair has exactly one implementation, making code predictable regardless of which crates are combined. The newtype pattern is the standard workaround when you need to implement a foreign trait for a foreign type.

Coherence is fundamental to Rust's trait system reliability and is why serde, std, and other foundational crates can co-exist without ambiguity.

🎯 Learning Outcomes

  • • Understand why the orphan rule exists (coherence guarantee, preventing ecosystem conflicts)
  • • Learn which combinations are allowed: (local trait, foreign type), (foreign trait, local type), but not (foreign trait, foreign type)
  • • See the newtype pattern as the standard workaround for the orphan rule
  • • Understand how impl Describable for i32 works because Describable is local
  • • Learn how blanket impls interact with coherence
  • Code Example

    #![allow(clippy::all)]
    //! Coherence and Orphan Rules
    
    // Can't impl Display for i32 (not our crate)
    // Can't impl foreign trait for foreign type
    
    // Newtype wrapper to work around orphan rule
    pub struct Wrapper<T>(pub T);
    
    impl std::fmt::Display for Wrapper<Vec<i32>> {
        fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
            write!(
                f,
                "[{}]",
                self.0
                    .iter()
                    .map(|x| x.to_string())
                    .collect::<Vec<_>>()
                    .join(", ")
            )
        }
    }
    
    // Our trait can be implemented for foreign types
    pub trait Describable {
        fn describe(&self) -> String;
    }
    impl Describable for i32 {
        fn describe(&self) -> String {
            format!("integer: {}", self)
        }
    }
    impl Describable for String {
        fn describe(&self) -> String {
            format!("string: {}", self)
        }
    }
    impl<T: Describable> Describable for Vec<T> {
        fn describe(&self) -> String {
            format!("vec of {} items", self.len())
        }
    }
    
    #[cfg(test)]
    mod tests {
        use super::*;
    
        #[test]
        fn test_wrapper_display() {
            let w = Wrapper(vec![1, 2, 3]);
            assert_eq!(format!("{}", w), "[1, 2, 3]");
        }
        #[test]
        fn test_i32_describe() {
            assert!(42.describe().contains("integer"));
        }
        #[test]
        fn test_string_describe() {
            assert!("hi".to_string().describe().contains("string"));
        }
        #[test]
        fn test_vec_describe() {
            assert!(vec![1, 2, 3].describe().contains("3 items"));
        }
    }

    Key Differences

  • Coherence guarantee: Rust has global coherence — one impl per (trait, type) pair; OCaml relies on lexical scoping and open ordering.
  • Conflict resolution: Rust makes impl conflicts a compile error; OCaml silently shadows with the most recently opened module.
  • Orphan rule: Rust enforces it; OCaml has no equivalent — any function can be defined for any type anywhere.
  • Newtype necessity: Rust requires newtypes to work around orphan restrictions; OCaml can directly add functions to any type's module.
  • OCaml Approach

    OCaml has no orphan rule. Any module can provide any function for any type. This flexibility enables convenience but can create conflicts — if two modules both open'd provide to_string : int -> string, the last one shadows the other. OCaml resolves conflicts through module shadowing and explicit qualification (Module.function), accepting ambiguity at the cost of explicit resolution.

    Full Source

    #![allow(clippy::all)]
    //! Coherence and Orphan Rules
    
    // Can't impl Display for i32 (not our crate)
    // Can't impl foreign trait for foreign type
    
    // Newtype wrapper to work around orphan rule
    pub struct Wrapper<T>(pub T);
    
    impl std::fmt::Display for Wrapper<Vec<i32>> {
        fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
            write!(
                f,
                "[{}]",
                self.0
                    .iter()
                    .map(|x| x.to_string())
                    .collect::<Vec<_>>()
                    .join(", ")
            )
        }
    }
    
    // Our trait can be implemented for foreign types
    pub trait Describable {
        fn describe(&self) -> String;
    }
    impl Describable for i32 {
        fn describe(&self) -> String {
            format!("integer: {}", self)
        }
    }
    impl Describable for String {
        fn describe(&self) -> String {
            format!("string: {}", self)
        }
    }
    impl<T: Describable> Describable for Vec<T> {
        fn describe(&self) -> String {
            format!("vec of {} items", self.len())
        }
    }
    
    #[cfg(test)]
    mod tests {
        use super::*;
    
        #[test]
        fn test_wrapper_display() {
            let w = Wrapper(vec![1, 2, 3]);
            assert_eq!(format!("{}", w), "[1, 2, 3]");
        }
        #[test]
        fn test_i32_describe() {
            assert!(42.describe().contains("integer"));
        }
        #[test]
        fn test_string_describe() {
            assert!("hi".to_string().describe().contains("string"));
        }
        #[test]
        fn test_vec_describe() {
            assert!(vec![1, 2, 3].describe().contains("3 items"));
        }
    }
    ✓ Tests Rust test suite
    #[cfg(test)]
    mod tests {
        use super::*;
    
        #[test]
        fn test_wrapper_display() {
            let w = Wrapper(vec![1, 2, 3]);
            assert_eq!(format!("{}", w), "[1, 2, 3]");
        }
        #[test]
        fn test_i32_describe() {
            assert!(42.describe().contains("integer"));
        }
        #[test]
        fn test_string_describe() {
            assert!("hi".to_string().describe().contains("string"));
        }
        #[test]
        fn test_vec_describe() {
            assert!(vec![1, 2, 3].describe().contains("3 items"));
        }
    }

    Deep Comparison

    OCaml vs Rust: 399-coherence-orphan-rules

    Exercises

  • Orphan rule exploration: Attempt to implement std::fmt::Display for std::collections::HashMap<String, i32>. Observe the compiler error. Then create DisplayMap(HashMap<String, i32>) newtype and implement Display on it instead.
  • Blanket impl limits: Implement trait Summary with a blanket impl<T: Display> Summary for T. Then try to add a second blanket impl<T: Debug> Summary for T. Observe the coherence error and explain why it occurs.
  • Crate simulation: Create a module lib_a with trait Printable {} and a module lib_b with struct Point { x: f32, y: f32 }. In your main module, implement Printable for Point and explain why this satisfies the orphan rule.
  • Open Source Repos