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
- `format!` and `println!` integration: your type works in any string formatting context without ceremony.
- Generic display functions: write `fn show_all<T: Display>(items: &[T])` once โ works for your type and every other `Display` type.
- Error messages and logging: when your domain types implement `Display`, error messages can embed them directly: `format!("Failed to process {}", my_value)`.
Key Differences
| Concept | OCaml | Rust |
|---|---|---|
| User-facing representation | Custom `to_string` or `pp` function | `impl fmt::Display` |
| Developer/debug output | Custom `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 types | Manual 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!