๐Ÿฆ€ Functional Rust

083: Display Trait

Difficulty: 2 Level: Intermediate Implement `fmt::Display` for your type so it works with `println!("{}")`, `format!()`, and `.to_string()` โ€” the standard Rust vocabulary for user-facing string representation.

The Problem This Solves

Every custom type needs two kinds of string representation: one for developers (debug output, logs) and one for users (UI, reports, messages). Rust separates these cleanly: `Debug` (via `{:?}`) is for developers and can be derived automatically; `Display` (via `{}`) is for users and you implement it yourself. Without `Display`, you can't use your type in `println!("{}", my_value)`, can't pass it to `format!`, and can't call `.to_string()`. You'd end up writing ad-hoc `to_string` methods that return `String` โ€” scattered, inconsistent, and not interoperable with the rest of the ecosystem. Implementing `Display` once gives you all of this for free, and any function parameterized on `T: Display` can work with your type automatically.

The Intuition

In Python, you implement `__str__` for user-facing output and `__repr__` for developer output. In Java, you override `toString()`. In OCaml, you write a `pp` function or use `Format.formatter`. In Rust, `Display` is the standard that the whole ecosystem depends on โ€” implement it and your type integrates everywhere. The key: `Display` uses a `Formatter` rather than returning a `String`. This is a performance choice โ€” you write directly into the output buffer, no intermediate allocation required. The `write!(f, ...)` macro inside your `fmt` method works just like `println!` but targets the formatter.

How It Works in Rust

use std::fmt;

struct Point { x: f64, y: f64 }

// Display: for users โ€” "(3.14, 2.72)"
impl fmt::Display for Point {
 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
     write!(f, "({:.2}, {:.2})", self.x, self.y)
 }
}

// Debug: for developers โ€” derive it when possible
#[derive(Debug)]
struct Point { x: f64, y: f64 }
// Enum Display: match on variant, write different strings
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"),
     }
 }
}
// Multi-line Display: use writeln! for intermediate lines, write! for the last
impl fmt::Display for Matrix {
 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
     for (i, row) in self.data.iter().enumerate() {
         write!(f, "| ")?;                          // ? propagates errors
         for val in row { write!(f, "{:6.2} ", val)?; }
         write!(f, "|")?;
         if i < self.data.len() - 1 { writeln!(f)?; }  // newline between rows
     }
     Ok(())
 }
}
// Recursive Display for trees โ€” works because T: Display is a bound
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),  // recursive
     }
 }
}
Once `Display` is implemented, `.to_string()` is available for free โ€” it's automatically provided by the `ToString` trait, which is blanket-implemented for all `Display` types.

What This Unlocks

Key Differences

ConceptOCamlRust
User-facing representationCustom `to_string` or `pp` function`impl fmt::Display`
Developer/debug outputCustom `show` or `Format.fprintf``#[derive(Debug)]` + `{:?}`
String conversion`string_of_*` functions`.to_string()` (free from `Display`)
Format destination`Format.formatter` or `Buffer.t``fmt::Formatter` (write with `write!`)
Recursive typesManual recursive `pp`Recursive `fmt` call โ€” `write!(f, "{}", child)`
// 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(", ")
}

fn main() {
    println!("Colors: {}, {}, {}", Color::Red, Color::Green, Color::Blue);

    let p = Point { x: 3.14, y: 2.72 };
    println!("Display: {}", p);
    println!("Debug: {:?}", p);
    println!("to_string: {}", p.to_string());

    let m = Matrix::new(vec![vec![1.0, 2.0], vec![3.0, 4.0]]);
    println!("Matrix:\n{}", m);

    let tree = Tree::node(
        Tree::node(Tree::Leaf, 1, Tree::Leaf),
        2,
        Tree::node(Tree::Leaf, 3, Tree::Leaf),
    );
    println!("Tree: {}", tree);

    let colors = vec![Color::Red, Color::Green, Color::Blue];
    println!("All: {}", print_all(&colors));
}

#[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");
    }
}
(* Example 083: Display Trait *)
(* OCaml to_string โ†’ Rust fmt::Display *)

(* Approach 1: Custom to_string functions *)
type color = Red | Green | Blue

let color_to_string = function
  | Red -> "Red"
  | Green -> "Green"
  | Blue -> "Blue"

type point = { x : float; y : float }

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

(* Approach 2: Using Format module (like Rust's fmt) *)
type matrix = float array array

let matrix_to_string m =
  let rows = Array.map (fun row ->
    let cells = Array.map (fun v -> Printf.sprintf "%6.2f" v) row in
    "| " ^ String.concat " " (Array.to_list cells) ^ " |"
  ) m in
  String.concat "\n" (Array.to_list rows)

(* Approach 3: Recursive/nested display *)
type 'a tree = Leaf | Node of 'a tree * 'a * 'a tree

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)

(* Printf-compatible custom printer *)
let pp_point fmt p =
  Format.fprintf fmt "(%.2f, %.2f)" p.x p.y

let pp_color fmt c =
  Format.fprintf fmt "%s" (color_to_string c)

(* Tests *)
let () =
  assert (color_to_string Red = "Red");
  assert (color_to_string Blue = "Blue");

  let p = { x = 3.14; y = 2.72 } in
  assert (point_to_string p = "(3.14, 2.72)");

  let m = [| [| 1.0; 2.0 |]; [| 3.0; 4.0 |] |] in
  let s = matrix_to_string m in
  assert (String.length s > 0);

  let tree = Node (Node (Leaf, 1, Leaf), 2, Node (Leaf, 3, Leaf)) in
  let s = tree_to_string string_of_int tree in
  assert (s = "((. 1 .) 2 (. 3 .))");

  Printf.printf "โœ“ All tests passed\n"

๐Ÿ“Š Detailed Comparison

Comparison: Display Trait

Simple Types

OCaml:

๐Ÿช Show OCaml equivalent
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:

๐Ÿช Show OCaml equivalent
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

๐Ÿช Show OCaml equivalent
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!