ExamplesBy LevelBy TopicLearning Paths
388 Intermediate

388: Extension Trait Pattern

Functional Programming

Tutorial Video

Text description (accessibility)

This video demonstrates the "388: Extension Trait Pattern" functional Rust example. Difficulty level: Intermediate. Key concepts covered: Functional Programming. You want to add methods to a type you don't own — `str`, `Vec`, an external library type. Key difference from OCaml: 1. **Scoping**: Rust extension methods only appear when the trait is in scope (`use StrExt`); OCaml module functions are always available when the module is open.

Tutorial

The Problem

You want to add methods to a type you don't own — str, Vec, an external library type. Rust's orphan rule prevents implementing foreign traits on foreign types directly, but the extension trait pattern works around this: define a new trait in your crate, implement it for the foreign type, and bring it into scope with use. This is how itertools adds hundreds of methods to Iterator, how tokio adds async utilities to TcpStream, and how domain-specific libraries enrich standard library types.

The extension trait pattern is foundational to Rust's "composable without modification" philosophy and powers rayon's ParallelIterator, bytes::BufMut, and serde's derived impls.

🎯 Learning Outcomes

  • • Understand the extension trait pattern as a solution to the orphan rule constraint
  • • Learn how to implement traits for foreign types (str, Vec<T>) in your crate
  • • See how use MyExt brings extension methods into scope at the call site
  • • Understand the ergonomics: extension methods appear alongside native methods in IDE completion
  • • Learn when extension traits are appropriate vs. free functions
  • Code Example

    #![allow(clippy::all)]
    //! Extension Trait Pattern
    
    pub trait StrExt {
        fn word_count(&self) -> usize;
        fn capitalize_words(&self) -> String;
        fn is_palindrome(&self) -> bool;
    }
    
    impl StrExt for str {
        fn word_count(&self) -> usize {
            self.split_whitespace().count()
        }
        fn capitalize_words(&self) -> String {
            self.split_whitespace()
                .map(|w| {
                    let mut c = w.chars();
                    match c.next() {
                        None => String::new(),
                        Some(f) => f.to_uppercase().to_string() + c.as_str(),
                    }
                })
                .collect::<Vec<_>>()
                .join(" ")
        }
        fn is_palindrome(&self) -> bool {
            let chars: Vec<char> = self.chars().collect();
            chars.iter().eq(chars.iter().rev())
        }
    }
    
    pub trait VecExt<T> {
        fn second(&self) -> Option<&T>;
    }
    impl<T> VecExt<T> for Vec<T> {
        fn second(&self) -> Option<&T> {
            self.get(1)
        }
    }
    
    #[cfg(test)]
    mod tests {
        use super::*;
    
        #[test]
        fn test_word_count() {
            assert_eq!("hello world".word_count(), 2);
        }
        #[test]
        fn test_capitalize() {
            assert_eq!("hello world".capitalize_words(), "Hello World");
        }
        #[test]
        fn test_palindrome() {
            assert!("racecar".is_palindrome());
            assert!(!"hello".is_palindrome());
        }
        #[test]
        fn test_second() {
            assert_eq!(vec![1, 2, 3].second(), Some(&2));
        }
    }

    Key Differences

  • Scoping: Rust extension methods only appear when the trait is in scope (use StrExt); OCaml module functions are always available when the module is open.
  • Discoverability: Rust IDEs show extension methods alongside native methods in completion; OCaml requires knowing which module to open.
  • Conflict resolution: If two extension traits add the same method name, Rust requires explicit disambiguation (StrExt::word_count(&s)); OCaml resolves by module shadowing (last open wins).
  • Ownership of impl: Rust requires your crate to own either the trait or the type; OCaml has no such restriction.
  • OCaml Approach

    OCaml achieves extension through module inclusion: module StringExt = struct include String; let word_count s = ... end. This creates a new module wrapping the original with added functions. Unlike Rust's extension traits, OCaml's approach produces a new module rather than patching the original type's method namespace. There are no orphan restrictions since OCaml's type system doesn't tie methods to types.

    Full Source

    #![allow(clippy::all)]
    //! Extension Trait Pattern
    
    pub trait StrExt {
        fn word_count(&self) -> usize;
        fn capitalize_words(&self) -> String;
        fn is_palindrome(&self) -> bool;
    }
    
    impl StrExt for str {
        fn word_count(&self) -> usize {
            self.split_whitespace().count()
        }
        fn capitalize_words(&self) -> String {
            self.split_whitespace()
                .map(|w| {
                    let mut c = w.chars();
                    match c.next() {
                        None => String::new(),
                        Some(f) => f.to_uppercase().to_string() + c.as_str(),
                    }
                })
                .collect::<Vec<_>>()
                .join(" ")
        }
        fn is_palindrome(&self) -> bool {
            let chars: Vec<char> = self.chars().collect();
            chars.iter().eq(chars.iter().rev())
        }
    }
    
    pub trait VecExt<T> {
        fn second(&self) -> Option<&T>;
    }
    impl<T> VecExt<T> for Vec<T> {
        fn second(&self) -> Option<&T> {
            self.get(1)
        }
    }
    
    #[cfg(test)]
    mod tests {
        use super::*;
    
        #[test]
        fn test_word_count() {
            assert_eq!("hello world".word_count(), 2);
        }
        #[test]
        fn test_capitalize() {
            assert_eq!("hello world".capitalize_words(), "Hello World");
        }
        #[test]
        fn test_palindrome() {
            assert!("racecar".is_palindrome());
            assert!(!"hello".is_palindrome());
        }
        #[test]
        fn test_second() {
            assert_eq!(vec![1, 2, 3].second(), Some(&2));
        }
    }
    ✓ Tests Rust test suite
    #[cfg(test)]
    mod tests {
        use super::*;
    
        #[test]
        fn test_word_count() {
            assert_eq!("hello world".word_count(), 2);
        }
        #[test]
        fn test_capitalize() {
            assert_eq!("hello world".capitalize_words(), "Hello World");
        }
        #[test]
        fn test_palindrome() {
            assert!("racecar".is_palindrome());
            assert!(!"hello".is_palindrome());
        }
        #[test]
        fn test_second() {
            assert_eq!(vec![1, 2, 3].second(), Some(&2));
        }
    }

    Deep Comparison

    OCaml vs Rust: 388-extension-trait-pattern

    Exercises

  • Numeric extension: Define NumExt trait and implement it for i32 with methods clamp_to_range(lo: i32, hi: i32) -> i32, digits() -> Vec<u8>, and is_prime() -> bool.
  • Option extension: Create OptionExt<T> that adds ok_or_log(msg: &str) -> Option<T> (logs a warning when None) and map_or_default<U: Default, F: Fn(T) -> U>(self, f: F) -> U.
  • Iterator extension: Implement a StatsExt trait for Iterator<Item = f64> that adds mean() -> Option<f64>, variance() -> Option<f64>, and histogram(buckets: usize) -> Vec<usize> methods, consuming the iterator.
  • Open Source Repos