ExamplesBy LevelBy TopicLearning Paths
403 Intermediate

403: Display and Debug Traits

Functional Programming

Tutorial Video

Text description (accessibility)

This video demonstrates the "403: Display and Debug Traits" functional Rust example. Difficulty level: Intermediate. Key concepts covered: Functional Programming. Formatting output for different audiences requires different representations. Key difference from OCaml: 1. **Automatic derivation**: Rust's `#[derive(Debug)]` generates complete implementations; OCaml's `deriving show` ppx extension provides similar capability.

Tutorial

The Problem

Formatting output for different audiences requires different representations. Debug output should be complete and unambiguous for developers (Color::Rgb(255, 0, 0)); display output should be readable for end users (rgb(255,0,0)). Mixing these leads to either confusing user-facing output or opaque developer output. Rust separates these concerns with two traits: Debug (derivable, for {:?}) and Display (manual, for {}). Additional formatter traits (LowerHex, Binary, Octet, Pointer) handle domain-specific representations.

Display and Debug are the entry points to all of Rust's format machinery — format!, println!, write!, eprintln!, and assert_eq! error messages all rely on them.

🎯 Learning Outcomes

  • • Understand the semantic difference between Debug (developer) and Display (user) formatting
  • • Learn how to implement fmt::Display with fmt::Formatter and write! macro
  • • See how #[derive(Debug)] generates a complete structural representation automatically
  • • Understand how to implement additional format traits like LowerHex for {:x} support
  • • Learn how fmt::Formatter provides alignment, width, fill, and precision parameters
  • Code Example

    use std::fmt;
    
    #[derive(Debug)]  // Auto-generates Debug
    enum Color {
        Red,
        Green,
        Blue,
        Rgb(u8, u8, u8),
    }
    
    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"),
                Color::Rgb(r, g, b) => write!(f, "rgb({},{},{})", r, g, b),
            }
        }
    }
    
    fn main() {
        let c = Color::Red;
        println!("display: {}  debug: {:?}", c, c);
    }

    Key Differences

  • Automatic derivation: Rust's #[derive(Debug)] generates complete implementations; OCaml's deriving show ppx extension provides similar capability.
  • Two-trait split: Rust maintains Debug and Display as separate traits; OCaml uses one pp convention, relying on programmer discipline.
  • Format string integration: Rust uses format!("{}", x) for Display and format!("{:?}", x) for Debug; OCaml uses %a with explicit pp functions.
  • Fmt trait family: Rust has Debug, Display, Binary, Octal, LowerHex, UpperHex, LowerExp, UpperExp, Pointer — a full family; OCaml relies on Printf format strings.
  • OCaml Approach

    OCaml has Printf.printf "%s" for string formatting and Format.pp_print_* functions for the Format module's structured pretty-printing. Custom types implement pp : Format.formatter -> t -> unit functions used with %a in format strings. There is no Display/Debug split — the same pp function serves both roles, though libraries conventionally have pp (compact) and show (verbose) variants.

    Full Source

    #![allow(clippy::all)]
    //! Display and Debug Traits
    //!
    //! Two formatting traits: Debug for developers, Display for users.
    
    use std::fmt;
    
    /// A color type demonstrating Display, Debug, and LowerHex formatting.
    #[derive(Debug, Clone, PartialEq)]
    pub enum Color {
        Red,
        Green,
        Blue,
        Rgb(u8, u8, u8),
    }
    
    impl Color {
        /// Creates a color from RGB values.
        pub fn rgb(r: u8, g: u8, b: u8) -> Self {
            Color::Rgb(r, g, b)
        }
    
        /// Returns the RGB components.
        pub fn to_rgb(&self) -> (u8, u8, u8) {
            match self {
                Color::Red => (255, 0, 0),
                Color::Green => (0, 255, 0),
                Color::Blue => (0, 0, 255),
                Color::Rgb(r, g, b) => (*r, *g, *b),
            }
        }
    }
    
    /// Display: user-facing output (used by `{}` and `println!`)
    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"),
                Color::Rgb(r, g, b) => write!(f, "rgb({},{},{})", r, g, b),
            }
        }
    }
    
    /// LowerHex: hexadecimal format (used by `{:x}`)
    impl fmt::LowerHex for Color {
        fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
            let (r, g, b) = self.to_rgb();
            write!(f, "#{:02x}{:02x}{:02x}", r, g, b)
        }
    }
    
    /// A 2D point demonstrating precision-aware Display.
    #[derive(Debug, Clone, PartialEq)]
    pub struct Point {
        pub x: f64,
        pub y: f64,
    }
    
    impl Point {
        /// Creates a new point.
        pub fn new(x: f64, y: f64) -> Self {
            Point { x, y }
        }
    
        /// Calculates distance from origin.
        pub fn distance_from_origin(&self) -> f64 {
            (self.x * self.x + self.y * self.y).sqrt()
        }
    }
    
    impl fmt::Display for Point {
        fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
            // Respect precision from format string if specified
            match f.precision() {
                Some(p) => write!(f, "({:.prec$}, {:.prec$})", self.x, self.y, prec = p),
                None => write!(f, "({:.2}, {:.2})", self.x, self.y),
            }
        }
    }
    
    /// A wrapper that formats its contents in binary.
    pub struct Binary<T>(pub T);
    
    impl fmt::Display for Binary<u8> {
        fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
            write!(f, "{:08b}", self.0)
        }
    }
    
    impl fmt::Display for Binary<u16> {
        fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
            write!(f, "{:016b}", self.0)
        }
    }
    
    /// A person struct showing the difference between Debug and Display.
    #[derive(Clone)]
    pub struct Person {
        pub name: String,
        pub age: u32,
    }
    
    impl Person {
        pub fn new(name: &str, age: u32) -> Self {
            Person {
                name: name.to_string(),
                age,
            }
        }
    }
    
    impl fmt::Display for Person {
        fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
            write!(f, "{} ({} years old)", self.name, self.age)
        }
    }
    
    impl fmt::Debug for Person {
        fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
            f.debug_struct("Person")
                .field("name", &self.name)
                .field("age", &self.age)
                .finish()
        }
    }
    
    /// Demonstrates alignment and padding.
    pub fn format_aligned(s: &str, width: usize) -> (String, String, String) {
        (
            format!("{:>width$}", s, width = width), // right-aligned
            format!("{:<width$}", s, width = width), // left-aligned
            format!("{:^width$}", s, width = width), // centered
        )
    }
    
    /// Demonstrates numeric formatting.
    pub fn format_number(n: u32) -> String {
        format!("dec:{} hex:{:#x} oct:{:#o} bin:{:#b}", n, n, n, n)
    }
    
    #[cfg(test)]
    mod tests {
        use super::*;
    
        #[test]
        fn test_color_display() {
            assert_eq!(format!("{}", Color::Red), "red");
            assert_eq!(format!("{}", Color::Green), "green");
            assert_eq!(format!("{}", Color::Blue), "blue");
            assert_eq!(format!("{}", Color::Rgb(10, 20, 30)), "rgb(10,20,30)");
        }
    
        #[test]
        fn test_color_debug() {
            assert_eq!(format!("{:?}", Color::Red), "Red");
            assert_eq!(format!("{:?}", Color::Rgb(1, 2, 3)), "Rgb(1, 2, 3)");
        }
    
        #[test]
        fn test_color_hex() {
            assert_eq!(format!("{:x}", Color::Red), "#ff0000");
            assert_eq!(format!("{:x}", Color::Green), "#00ff00");
            assert_eq!(format!("{:x}", Color::Blue), "#0000ff");
            assert_eq!(format!("{:x}", Color::Rgb(128, 64, 32)), "#804020");
        }
    
        #[test]
        fn test_color_to_rgb() {
            assert_eq!(Color::Red.to_rgb(), (255, 0, 0));
            assert_eq!(Color::rgb(100, 150, 200).to_rgb(), (100, 150, 200));
        }
    
        #[test]
        fn test_point_display_default() {
            let p = Point::new(3.14159, 2.71828);
            assert_eq!(format!("{}", p), "(3.14, 2.72)");
        }
    
        #[test]
        fn test_point_display_precision() {
            let p = Point::new(1.23456, 7.89012);
            assert_eq!(format!("{:.1}", p), "(1.2, 7.9)");
            assert_eq!(format!("{:.4}", p), "(1.2346, 7.8901)");
        }
    
        #[test]
        fn test_point_debug() {
            let p = Point::new(1.0, 2.0);
            assert_eq!(format!("{:?}", p), "Point { x: 1.0, y: 2.0 }");
        }
    
        #[test]
        fn test_binary_display() {
            assert_eq!(format!("{}", Binary(5u8)), "00000101");
            assert_eq!(format!("{}", Binary(255u8)), "11111111");
        }
    
        #[test]
        fn test_person_display_vs_debug() {
            let p = Person::new("Alice", 30);
            assert_eq!(format!("{}", p), "Alice (30 years old)");
            assert_eq!(format!("{:?}", p), "Person { name: \"Alice\", age: 30 }");
        }
    
        #[test]
        fn test_format_aligned() {
            let (right, left, center) = format_aligned("hi", 6);
            assert_eq!(right, "    hi");
            assert_eq!(left, "hi    ");
            assert_eq!(center, "  hi  ");
        }
    
        #[test]
        fn test_format_number() {
            let s = format_number(255);
            assert!(s.contains("dec:255"));
            assert!(s.contains("hex:0xff"));
            assert!(s.contains("oct:0o377"));
            assert!(s.contains("bin:0b11111111"));
        }
    
        #[test]
        fn test_pretty_debug() {
            let p = Point::new(1.0, 2.0);
            let pretty = format!("{:#?}", p);
            assert!(pretty.contains("Point"));
            assert!(pretty.contains("x:"));
            assert!(pretty.contains("y:"));
        }
    }
    ✓ 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");
            assert_eq!(format!("{}", Color::Blue), "blue");
            assert_eq!(format!("{}", Color::Rgb(10, 20, 30)), "rgb(10,20,30)");
        }
    
        #[test]
        fn test_color_debug() {
            assert_eq!(format!("{:?}", Color::Red), "Red");
            assert_eq!(format!("{:?}", Color::Rgb(1, 2, 3)), "Rgb(1, 2, 3)");
        }
    
        #[test]
        fn test_color_hex() {
            assert_eq!(format!("{:x}", Color::Red), "#ff0000");
            assert_eq!(format!("{:x}", Color::Green), "#00ff00");
            assert_eq!(format!("{:x}", Color::Blue), "#0000ff");
            assert_eq!(format!("{:x}", Color::Rgb(128, 64, 32)), "#804020");
        }
    
        #[test]
        fn test_color_to_rgb() {
            assert_eq!(Color::Red.to_rgb(), (255, 0, 0));
            assert_eq!(Color::rgb(100, 150, 200).to_rgb(), (100, 150, 200));
        }
    
        #[test]
        fn test_point_display_default() {
            let p = Point::new(3.14159, 2.71828);
            assert_eq!(format!("{}", p), "(3.14, 2.72)");
        }
    
        #[test]
        fn test_point_display_precision() {
            let p = Point::new(1.23456, 7.89012);
            assert_eq!(format!("{:.1}", p), "(1.2, 7.9)");
            assert_eq!(format!("{:.4}", p), "(1.2346, 7.8901)");
        }
    
        #[test]
        fn test_point_debug() {
            let p = Point::new(1.0, 2.0);
            assert_eq!(format!("{:?}", p), "Point { x: 1.0, y: 2.0 }");
        }
    
        #[test]
        fn test_binary_display() {
            assert_eq!(format!("{}", Binary(5u8)), "00000101");
            assert_eq!(format!("{}", Binary(255u8)), "11111111");
        }
    
        #[test]
        fn test_person_display_vs_debug() {
            let p = Person::new("Alice", 30);
            assert_eq!(format!("{}", p), "Alice (30 years old)");
            assert_eq!(format!("{:?}", p), "Person { name: \"Alice\", age: 30 }");
        }
    
        #[test]
        fn test_format_aligned() {
            let (right, left, center) = format_aligned("hi", 6);
            assert_eq!(right, "    hi");
            assert_eq!(left, "hi    ");
            assert_eq!(center, "  hi  ");
        }
    
        #[test]
        fn test_format_number() {
            let s = format_number(255);
            assert!(s.contains("dec:255"));
            assert!(s.contains("hex:0xff"));
            assert!(s.contains("oct:0o377"));
            assert!(s.contains("bin:0b11111111"));
        }
    
        #[test]
        fn test_pretty_debug() {
            let p = Point::new(1.0, 2.0);
            let pretty = format!("{:#?}", p);
            assert!(pretty.contains("Point"));
            assert!(pretty.contains("x:"));
            assert!(pretty.contains("y:"));
        }
    }

    Deep Comparison

    OCaml vs Rust: Display and Debug Traits

    Side-by-Side Code

    OCaml — Manual to_string functions

    type color = Red | Green | Blue | Rgb of int * int * int
    
    let string_of_color = function
      | Red -> "red"
      | Green -> "green"
      | Blue -> "blue"
      | Rgb (r, g, b) -> Printf.sprintf "rgb(%d,%d,%d)" r g b
    
    let debug_color = function
      | Red -> "Color::Red"
      | Green -> "Color::Green"
      | Blue -> "Color::Blue"
      | Rgb (r, g, b) -> Printf.sprintf "Color::Rgb(%d, %d, %d)" r g b
    
    let () =
      Printf.printf "display: %s  debug: %s\n"
        (string_of_color Red) (debug_color Red)
    

    Rust — Traits with automatic integration

    use std::fmt;
    
    #[derive(Debug)]  // Auto-generates Debug
    enum Color {
        Red,
        Green,
        Blue,
        Rgb(u8, u8, u8),
    }
    
    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"),
                Color::Rgb(r, g, b) => write!(f, "rgb({},{},{})", r, g, b),
            }
        }
    }
    
    fn main() {
        let c = Color::Red;
        println!("display: {}  debug: {:?}", c, c);
    }
    

    Comparison Table

    AspectOCamlRust
    User-facing formatstring_of_* functionDisplay trait, {} placeholder
    Developer formatSeparate debug_* functionDebug trait, {:?} placeholder
    Auto-derivation[@@deriving show] (ppx)#[derive(Debug)] (built-in)
    IntegrationManual function callsAutomatic via println!, format!
    Pretty printingManual formatting{:#?} for multi-line
    Custom formattersMore functionsMore traits: LowerHex, Binary, etc.

    Format Specifiers

    Rust's formatting system supports many specifiers:

    let n = 255;
    println!("{}", n);      // Display: 255
    println!("{:?}", n);    // Debug: 255
    println!("{:x}", n);    // LowerHex: ff
    println!("{:X}", n);    // UpperHex: FF
    println!("{:o}", n);    // Octal: 377
    println!("{:b}", n);    // Binary: 11111111
    println!("{:e}", 1.5);  // LowerExp: 1.5e0
    

    OCaml uses Printf with different format strings:

    Printf.printf "%d %x %o\n" 255 255 255
    (* 255 ff 377 *)
    

    The Formatter Parameter

    Rust's fmt::Formatter provides formatting options:

    impl fmt::Display for Point {
        fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
            // f.precision() — requested decimal places
            // f.width() — requested field width
            // f.fill() — padding character
            // f.align() — Left, Right, Center
            match f.precision() {
                Some(p) => write!(f, "({:.prec$}, {:.prec$})", self.x, self.y, prec = p),
                None => write!(f, "({:.2}, {:.2})", self.x, self.y),
            }
        }
    }
    
    // Now users can control precision:
    println!("{:.4}", point);  // 4 decimal places
    

    5 Takeaways

  • **#[derive(Debug)] is free and should be used almost everywhere.**
  • It's a one-liner that enables {:?} printing.

  • **Display is for users, Debug is for developers.**
  • Display should be human-readable; Debug should be useful for debugging.

  • OCaml's approach is more explicit, less magical.
  • You write functions and call them. Rust uses trait dispatch.

  • **Rust's formatter traits integrate with println! and format!.**
  • No need to call conversion functions — use format specifiers.

  • Custom format specifiers are possible via additional traits.
  • LowerHex, Binary, Octal, etc. — implement what makes sense for your type.

    Exercises

  • Pretty matrix: Implement Display for a Matrix<f64> that formats as aligned columns (use write!(f, "{:8.3}", val) width specifier) and Debug showing the raw flat array.
  • Recursive tree: Implement Display for a Tree<i32> (leaf or node with two children) using indentation — leaves on their own line, nodes with children indented 2 spaces. Use fmt::Formatter::write_str for each line.
  • Custom format trait: Implement fmt::Binary for a Bitset(u64) type, formatting it as 0b{bits} with the set bits indicated. Implement fmt::Display to show just the set bit positions as {1, 3, 7}.
  • Open Source Repos