ExamplesBy LevelBy TopicLearning Paths
083 Intermediate

083 — Display Trait

Functional Programming

Tutorial

The Problem

Implement std::fmt::Display for custom types — Color, Point, Person, and a generic Tree<T> — to enable format!, println!, and to_string() without deriving Debug. Compare with OCaml's Printf.sprintf-based to_string functions for the same types.

🎯 Learning Outcomes

  • • Implement fmt::Display using write!(f, "...", ...) in the fmt method
  • • Understand fmt::Formatter as the sink that write! targets
  • • Use format specifiers like {:.1} for floating-point precision inside Display
  • • Implement Display for generic types with T: fmt::Display bound
  • • Distinguish Display (user-facing) from Debug (developer-facing)
  • • Map Rust's trait-based formatting to OCaml's explicit to_string functions
  • Code Example

    #![allow(clippy::all)]
    // 083: Display Trait
    // Implement Display for custom types
    
    use std::fmt;
    
    // Approach 1: Simple Display
    #[derive(Debug)]
    enum Color {
        Red,
        Green,
        Blue,
    }
    
    impl fmt::Display for Color {
        fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
            match self {
                Color::Red => write!(f, "Red"),
                Color::Green => write!(f, "Green"),
                Color::Blue => write!(f, "Blue"),
            }
        }
    }
    
    #[derive(Debug)]
    struct Point {
        x: f64,
        y: f64,
    }
    
    impl fmt::Display for Point {
        fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
            write!(f, "({:.1}, {:.1})", self.x, self.y)
        }
    }
    
    // Approach 2: Complex formatting
    #[derive(Debug)]
    struct Person {
        name: String,
        age: u32,
        email: String,
    }
    
    impl fmt::Display for Person {
        fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
            write!(f, "{} (age {}, {})", self.name, self.age, self.email)
        }
    }
    
    // Approach 3: Recursive Display
    #[derive(Debug)]
    enum Tree<T> {
        Leaf,
        Node(Box<Tree<T>>, T, Box<Tree<T>>),
    }
    
    impl<T: fmt::Display> fmt::Display for Tree<T> {
        fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
            match self {
                Tree::Leaf => write!(f, "."),
                Tree::Node(l, v, r) => write!(f, "({} {} {})", l, v, r),
            }
        }
    }
    
    #[cfg(test)]
    mod tests {
        use super::*;
    
        #[test]
        fn test_color_display() {
            assert_eq!(format!("{}", Color::Red), "Red");
            assert_eq!(format!("{}", Color::Green), "Green");
        }
    
        #[test]
        fn test_point_display() {
            assert_eq!(format!("{}", Point { x: 3.0, y: 4.0 }), "(3.0, 4.0)");
        }
    
        #[test]
        fn test_person_display() {
            let p = Person {
                name: "Alice".into(),
                age: 30,
                email: "alice@ex.com".into(),
            };
            assert_eq!(format!("{}", p), "Alice (age 30, alice@ex.com)");
        }
    
        #[test]
        fn test_tree_display() {
            let tree = Tree::Node(
                Box::new(Tree::Node(Box::new(Tree::Leaf), 1, Box::new(Tree::Leaf))),
                2,
                Box::new(Tree::Node(Box::new(Tree::Leaf), 3, Box::new(Tree::Leaf))),
            );
            assert_eq!(format!("{}", tree), "((. 1 .) 2 (. 3 .))");
        }
    }

    Key Differences

    AspectRustOCaml
    Interfaceimpl fmt::Display traitto_string function per type
    Generic elementsT: fmt::Display boundto_s : 'a -> string parameter
    Debug format#[derive(Debug)]Printf.sprintf "%S" / ppx
    to_string()Auto from DisplayExplicit function
    Sink typefmt::FormatterReturns string directly
    Format spec{:.1}, {:>10}, etc.%.1f, %10s, etc.

    Rust's Display trait integrates with the entire format!/println!/write! machinery. Any type that implements Display can be used in any format string {} position. OCaml's approach is more direct but requires explicitly threading the to_string function through generic code.

    OCaml Approach

    OCaml does not have a single Display trait; each type gets its own to_string function. Printf.sprintf "(%.1f, %.1f)" p.x p.y formats a point. For recursive tree, a higher-order tree_to_string to_s takes the element formatter as an argument, since OCaml has no trait-bound system. The result is the same string — the mechanism is different: explicit function passing vs trait dispatch.

    Full Source

    #![allow(clippy::all)]
    // 083: Display Trait
    // Implement Display for custom types
    
    use std::fmt;
    
    // Approach 1: Simple Display
    #[derive(Debug)]
    enum Color {
        Red,
        Green,
        Blue,
    }
    
    impl fmt::Display for Color {
        fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
            match self {
                Color::Red => write!(f, "Red"),
                Color::Green => write!(f, "Green"),
                Color::Blue => write!(f, "Blue"),
            }
        }
    }
    
    #[derive(Debug)]
    struct Point {
        x: f64,
        y: f64,
    }
    
    impl fmt::Display for Point {
        fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
            write!(f, "({:.1}, {:.1})", self.x, self.y)
        }
    }
    
    // Approach 2: Complex formatting
    #[derive(Debug)]
    struct Person {
        name: String,
        age: u32,
        email: String,
    }
    
    impl fmt::Display for Person {
        fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
            write!(f, "{} (age {}, {})", self.name, self.age, self.email)
        }
    }
    
    // Approach 3: Recursive Display
    #[derive(Debug)]
    enum Tree<T> {
        Leaf,
        Node(Box<Tree<T>>, T, Box<Tree<T>>),
    }
    
    impl<T: fmt::Display> fmt::Display for Tree<T> {
        fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
            match self {
                Tree::Leaf => write!(f, "."),
                Tree::Node(l, v, r) => write!(f, "({} {} {})", l, v, r),
            }
        }
    }
    
    #[cfg(test)]
    mod tests {
        use super::*;
    
        #[test]
        fn test_color_display() {
            assert_eq!(format!("{}", Color::Red), "Red");
            assert_eq!(format!("{}", Color::Green), "Green");
        }
    
        #[test]
        fn test_point_display() {
            assert_eq!(format!("{}", Point { x: 3.0, y: 4.0 }), "(3.0, 4.0)");
        }
    
        #[test]
        fn test_person_display() {
            let p = Person {
                name: "Alice".into(),
                age: 30,
                email: "alice@ex.com".into(),
            };
            assert_eq!(format!("{}", p), "Alice (age 30, alice@ex.com)");
        }
    
        #[test]
        fn test_tree_display() {
            let tree = Tree::Node(
                Box::new(Tree::Node(Box::new(Tree::Leaf), 1, Box::new(Tree::Leaf))),
                2,
                Box::new(Tree::Node(Box::new(Tree::Leaf), 3, Box::new(Tree::Leaf))),
            );
            assert_eq!(format!("{}", tree), "((. 1 .) 2 (. 3 .))");
        }
    }
    ✓ Tests Rust test suite
    #[cfg(test)]
    mod tests {
        use super::*;
    
        #[test]
        fn test_color_display() {
            assert_eq!(format!("{}", Color::Red), "Red");
            assert_eq!(format!("{}", Color::Green), "Green");
        }
    
        #[test]
        fn test_point_display() {
            assert_eq!(format!("{}", Point { x: 3.0, y: 4.0 }), "(3.0, 4.0)");
        }
    
        #[test]
        fn test_person_display() {
            let p = Person {
                name: "Alice".into(),
                age: 30,
                email: "alice@ex.com".into(),
            };
            assert_eq!(format!("{}", p), "Alice (age 30, alice@ex.com)");
        }
    
        #[test]
        fn test_tree_display() {
            let tree = Tree::Node(
                Box::new(Tree::Node(Box::new(Tree::Leaf), 1, Box::new(Tree::Leaf))),
                2,
                Box::new(Tree::Node(Box::new(Tree::Leaf), 3, Box::new(Tree::Leaf))),
            );
            assert_eq!(format!("{}", tree), "((. 1 .) 2 (. 3 .))");
        }
    }

    Deep Comparison

    Core Insight

    Display controls how a type is printed with {}. Unlike Debug (derived), Display must be manually implemented — it's the user-facing representation.

    OCaml Approach

  • • Write to_string function manually
  • • Use Printf.sprintf with format strings
  • • No unified "display" protocol
  • Rust Approach

  • impl fmt::Display for Type
  • • Enables format!("{}", x), println!("{}", x)
  • • Single fmt method returns fmt::Result
  • Comparison Table

    FeatureOCamlRust
    ProtocolManual to_stringimpl Display
    Format string%s with to_string{} automatic
    Debug#show (ppx)#[derive(Debug)]
    DeriveNoDisplay: no, Debug: yes

    Exercises

  • Implement fmt::Display for a Matrix(Vec<Vec<f64>>) newtype that prints rows separated by newlines.
  • Add an impl fmt::Display for Tree<T> variant that prints in indented format (each level adds two spaces).
  • Implement fmt::Debug manually for Person to show Person { name: "Alice", age: 30, email: "…" }.
  • Write a Wrapper<T: Display>(Vec<T>) that displays its items as [a, b, c].
  • In OCaml, define a Printable module type with val to_string : t -> string and a functor PrintList(P: Printable) with val print_list : P.t list -> string.
  • Open Source Repos