ExamplesBy LevelBy TopicLearning Paths
877 Intermediate

877-display-trait — Display Trait

Functional Programming

Tutorial

The Problem

Every user-facing type eventually needs a string representation. In Java, toString() is defined on Object and returns String. In Python, __str__ serves the same role. Rust splits this into two distinct traits: Display for human-readable output ({} format) and Debug for developer-diagnostic output ({:?} format). This separation prevents the common bug of accidentally showing internal debug details to end users. OCaml uses to_string functions by convention (no enforced interface), or Format.fprintf for more complex formatting. Implementing Display unlocks format!, println!, to_string(), and any generic function bounded on Display.

🎯 Learning Outcomes

  • • Implement fmt::Display for custom types to enable {} formatting
  • • Distinguish between Display (user-facing) and Debug (developer-facing)
  • • Write multi-line and nested Display implementations for structured types
  • • Understand how implementing Display automatically provides to_string()
  • • Compare with OCaml's to_string convention and Format module
  • Code Example

    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"),
            }
        }
    }
    
    println!("{}", Color::Red);  // Display enables format!

    Key Differences

  • Trait vs convention: Rust Display is a formal trait enforced by the compiler; OCaml to_string is a naming convention with no type-system enforcement.
  • Automatic to_string: Rust derives to_string() for free from Display; OCaml requires explicit to_string implementation.
  • Format string integration: Rust's format!("{}", val) invokes Display; OCaml uses Printf.sprintf "%s" (to_string val) or Format.asprintf.
  • Debug vs Display: Rust enforces the distinction via two separate traits; OCaml has no built-in equivalent separation.
  • OCaml Approach

    OCaml has no enforced Display interface. Convention is to define a to_string: t -> string function per module. For complex formatting, the Format module provides fprintf, printf, and sprintf with %a for custom formatters. Format.pp_print_string, Format.pp_print_int etc. compose into pretty-printers. OCaml's [@@deriving show] (via ppx_deriving) can auto-generate show functions similar to Rust's #[derive(Debug)].

    Full Source

    #![allow(clippy::all)]
    // Example 083: Display Trait
    // OCaml to_string → Rust fmt::Display
    
    use std::fmt;
    
    // === Approach 1: Simple Display implementations ===
    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"),
            }
        }
    }
    
    struct Point {
        x: f64,
        y: f64,
    }
    
    impl fmt::Display for Point {
        fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
            write!(f, "({:.2}, {:.2})", self.x, self.y)
        }
    }
    
    // Debug is for developers, Display is for users
    impl fmt::Debug for Point {
        fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
            write!(f, "Point{{ x: {}, y: {} }}", self.x, self.y)
        }
    }
    
    // === Approach 2: Multi-line Display (matrix) ===
    struct Matrix {
        data: Vec<Vec<f64>>,
    }
    
    impl Matrix {
        fn new(data: Vec<Vec<f64>>) -> Self {
            Matrix { data }
        }
    }
    
    impl fmt::Display for Matrix {
        fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
            for (i, row) in self.data.iter().enumerate() {
                write!(f, "| ")?;
                for (j, val) in row.iter().enumerate() {
                    if j > 0 {
                        write!(f, " ")?;
                    }
                    write!(f, "{:6.2}", val)?;
                }
                write!(f, " |")?;
                if i < self.data.len() - 1 {
                    writeln!(f)?;
                }
            }
            Ok(())
        }
    }
    
    // === Approach 3: Recursive Display (tree) ===
    enum Tree<T> {
        Leaf,
        Node(Box<Tree<T>>, T, Box<Tree<T>>),
    }
    
    impl<T: fmt::Display> Tree<T> {
        fn node(left: Tree<T>, value: T, right: Tree<T>) -> Self {
            Tree::Node(Box::new(left), value, Box::new(right))
        }
    }
    
    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),
            }
        }
    }
    
    // Display enables .to_string() automatically
    fn print_all<T: fmt::Display>(items: &[T]) -> String {
        items
            .iter()
            .map(|i| i.to_string())
            .collect::<Vec<_>>()
            .join(", ")
    }
    
    #[cfg(test)]
    mod tests {
        use super::*;
    
        #[test]
        fn test_color_display() {
            assert_eq!(Color::Red.to_string(), "Red");
            assert_eq!(format!("{}", Color::Blue), "Blue");
        }
    
        #[test]
        fn test_point_display() {
            let p = Point { x: 1.0, y: 2.0 };
            assert_eq!(format!("{}", p), "(1.00, 2.00)");
        }
    
        #[test]
        fn test_point_debug() {
            let p = Point { x: 1.0, y: 2.0 };
            assert_eq!(format!("{:?}", p), "Point{ x: 1, y: 2 }");
        }
    
        #[test]
        fn test_matrix_display() {
            let m = Matrix::new(vec![vec![1.0, 2.0], vec![3.0, 4.0]]);
            let s = format!("{}", m);
            assert!(s.contains("1.00"));
            assert!(s.contains("4.00"));
        }
    
        #[test]
        fn test_tree_display() {
            let tree = Tree::node(Tree::Leaf, 42, Tree::Leaf);
            assert_eq!(format!("{}", tree), "(. 42 .)");
        }
    
        #[test]
        fn test_nested_tree() {
            let tree = Tree::node(
                Tree::node(Tree::Leaf, 1, Tree::Leaf),
                2,
                Tree::node(Tree::Leaf, 3, Tree::Leaf),
            );
            assert_eq!(format!("{}", tree), "((. 1 .) 2 (. 3 .))");
        }
    
        #[test]
        fn test_to_string() {
            let p = Point { x: 0.0, y: 0.0 };
            let s: String = p.to_string();
            assert_eq!(s, "(0.00, 0.00)");
        }
    
        #[test]
        fn test_print_all() {
            let colors = vec![Color::Red, Color::Green];
            assert_eq!(print_all(&colors), "Red, Green");
        }
    }
    ✓ Tests Rust test suite
    #[cfg(test)]
    mod tests {
        use super::*;
    
        #[test]
        fn test_color_display() {
            assert_eq!(Color::Red.to_string(), "Red");
            assert_eq!(format!("{}", Color::Blue), "Blue");
        }
    
        #[test]
        fn test_point_display() {
            let p = Point { x: 1.0, y: 2.0 };
            assert_eq!(format!("{}", p), "(1.00, 2.00)");
        }
    
        #[test]
        fn test_point_debug() {
            let p = Point { x: 1.0, y: 2.0 };
            assert_eq!(format!("{:?}", p), "Point{ x: 1, y: 2 }");
        }
    
        #[test]
        fn test_matrix_display() {
            let m = Matrix::new(vec![vec![1.0, 2.0], vec![3.0, 4.0]]);
            let s = format!("{}", m);
            assert!(s.contains("1.00"));
            assert!(s.contains("4.00"));
        }
    
        #[test]
        fn test_tree_display() {
            let tree = Tree::node(Tree::Leaf, 42, Tree::Leaf);
            assert_eq!(format!("{}", tree), "(. 42 .)");
        }
    
        #[test]
        fn test_nested_tree() {
            let tree = Tree::node(
                Tree::node(Tree::Leaf, 1, Tree::Leaf),
                2,
                Tree::node(Tree::Leaf, 3, Tree::Leaf),
            );
            assert_eq!(format!("{}", tree), "((. 1 .) 2 (. 3 .))");
        }
    
        #[test]
        fn test_to_string() {
            let p = Point { x: 0.0, y: 0.0 };
            let s: String = p.to_string();
            assert_eq!(s, "(0.00, 0.00)");
        }
    
        #[test]
        fn test_print_all() {
            let colors = vec![Color::Red, Color::Green];
            assert_eq!(print_all(&colors), "Red, Green");
        }
    }

    Deep Comparison

    Comparison: Display Trait

    Simple Types

    OCaml:

    type color = Red | Green | Blue
    
    let color_to_string = function
      | Red -> "Red" | Green -> "Green" | Blue -> "Blue"
    
    let () = Printf.printf "%s\n" (color_to_string Red)
    

    Rust:

    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"),
            }
        }
    }
    
    println!("{}", Color::Red);  // Display enables format!
    

    Recursive Types

    OCaml:

    let rec tree_to_string to_s = function
      | Leaf -> "."
      | Node (l, v, r) ->
        Printf.sprintf "(%s %s %s)" (tree_to_string to_s l) (to_s v) (tree_to_string to_s r)
    

    Rust:

    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),
            }
        }
    }
    

    to_string

    OCaml: Manual function, no standard trait

    let point_to_string p = Printf.sprintf "(%.2f, %.2f)" p.x p.y
    

    Rust: Automatic via Display

    impl fmt::Display for Point {
        fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
            write!(f, "({:.2}, {:.2})", self.x, self.y)
        }
    }
    let s = point.to_string();  // Free!
    

    Exercises

  • Implement Display for a FractionDisplay(i64, i64) type that shows fractions like 3/4 in simplified form.
  • Write a Table struct with a Vec<Vec<String>> grid and implement Display to render it with column alignment.
  • Implement a Duration type wrapping seconds and implement Display to show 1h 23m 45s formatted output.
  • Open Source Repos