399: Coherence and Orphan Rules
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
impl Describable for i32 works because Describable is localCode 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
(trait, type) pair; OCaml relies on lexical scoping and open ordering.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"));
}
}#[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
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.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.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.