877-display-trait — Display Trait
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
fmt::Display for custom types to enable {} formattingDisplay (user-facing) and Debug (developer-facing)Display implementations for structured typesDisplay automatically provides to_string()to_string convention and Format moduleCode 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
Display is a formal trait enforced by the compiler; OCaml to_string is a naming convention with no type-system enforcement.to_string() for free from Display; OCaml requires explicit to_string implementation.format!("{}", val) invokes Display; OCaml uses Printf.sprintf "%s" (to_string val) or Format.asprintf.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");
}
}#[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
Display for a FractionDisplay(i64, i64) type that shows fractions like 3/4 in simplified form.Table struct with a Vec<Vec<String>> grid and implement Display to render it with column alignment.Duration type wrapping seconds and implement Display to show 1h 23m 45s formatted output.