Display
cargo run --bin 21_display_default_deriveDisplay is the user-facing format used by {}. Implement it with write!(f, "..."). Every Display type gets to_string() for free through ToString, so never implement ToString yourself. Display has no derive: you decide how a value reads to a user.
Default::default() builds a reasonable empty value. #[derive(Default)] works when every field has a Default. Combined with struct update syntax it gives you "defaults, except these fields".
#[derive(...)] asks the compiler to write impls for you. The built-in derives are Clone, Copy, Debug, Default, PartialEq, Eq, PartialOrd, Ord, and Hash. A derive fails if a field does not implement the trait.
A minimal Display
struct Meter(i32);
impl fmt::Display for Meter {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}m", self.0)
}
}
fn demo_display() {
println!("-- Display --");
let m = Meter(12);
println!("{m}");
let s: String = m.to_string();
println!("to_string {s} len {}", s.len());
// println!("{:?}", m); // error[E0277]: `Meter` doesn't implement `Debug`
}Display that honors the caller's width and alignment
struct Money {
cents: i64,
}
impl fmt::Display for Money {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let sign = if self.cents < 0 { "-" } else { "" };
let abs = self.cents.abs();
let text = format!("{sign}${}.{:02}", abs / 100, abs % 100);
// pad respects width and alignment from the caller's format spec.
f.pad(&text)
}
}
fn demo_display_padding() {
println!("-- Display with padding --");
let items = [("coffee", Money { cents: 450 }), ("refund", Money { cents: -1299 })];
for (name, price) in &items {
println!("[{name:<8}] [{price:>10}]");
}
}Multi-line Display, and Debug next to it
#[derive(Debug)]
struct Matrix([[i32; 2]; 2]);
impl fmt::Display for Matrix {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
for row in &self.0 {
writeln!(f, "| {:>3} {:>3} |", row[0], row[1])?;
}
Ok(())
}
}
fn demo_display_vs_debug() {
println!("-- Display vs Debug --");
let m = Matrix([[1, 20], [300, 4]]);
print!("{m}");
println!("{m:?}");
// The alternate flag {:#} is available inside fmt as f.alternate().
}Derived Default
#[derive(Debug, Default)]
struct Cfg {
host: String,
port: u16,
verbose: bool,
tags: Vec<String>,
timeout: Option<u32>,
}
fn demo_default() {
println!("-- Default --");
let d = Cfg::default();
println!("{d:?}");
let c = Cfg { port: 8080, ..Default::default() };
println!("{c:?}");
println!(
"host {:?} port {} verbose {} tags {} timeout {:?}",
c.host,
c.port,
c.verbose,
c.tags.len(),
c.timeout
);
println!(
"std defaults: {:?} {:?} {:?} {:?}",
i32::default(),
String::default(),
<Option<u8>>::default(),
<(bool, char)>::default()
);
}Hand-written Default, and #[default] on an enum variant
#[derive(Debug)]
struct Server {
host: String,
port: u16,
workers: usize,
}
impl Default for Server {
fn default() -> Self {
Server { host: "0.0.0.0".into(), port: 80, workers: 4 }
}
}
#[derive(Debug, Default, PartialEq)]
enum Mode {
#[default]
Fast,
Safe,
}
fn demo_custom_default() {
println!("-- custom Default --");
let s = Server { port: 8443, ..Default::default() };
println!("{s:?}");
println!("{} {} {}", s.host, s.port, s.workers);
println!("mode {:?} safe? {}", Mode::default(), Mode::default() == Mode::Safe);
let counts: std::collections::HashMap<&str, i32> = Default::default();
println!("empty map {counts:?}");
}What derive generates
#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Default)]
struct Version {
major: u32,
minor: u32,
}
fn demo_derive() {
println!("-- derive --");
let a = Version { major: 1, minor: 2 };
let b = a.clone();
println!("{:?} == {:?}? {}", a, b, a == b);
let newer = Version { major: 1, minor: 10 };
// Derived ordering compares fields top to bottom.
println!("a < newer? {} max {:?}", a < newer, std::cmp::max(&a, &newer));
let mut set = std::collections::HashSet::new();
set.insert(a);
set.insert(b);
println!("set len {} default {:?}", set.len(), Version::default());
// #[derive(Clone)] struct NoClone { f: std::sync::MutexGuard<'static, ()> }
// error[E0277]: the trait bound `std::sync::MutexGuard<'_, ()>: Clone` is not satisfied
}$ cargo run --bin 21_display_default_derive -- Display -- 12m to_string 12m len 3 -- Display with padding -- [coffee ] [ $4.50] [refund ] [ -$12.99] -- Display vs Debug -- | 1 20 | | 300 4 | Matrix([[1, 20], [300, 4]]) -- Default -- Cfg { host: "", port: 0, verbose: false, tags: [], timeout: None } Cfg { host: "", port: 8080, verbose: false, tags: [], timeout: None } host "" port 8080 verbose false tags 0 timeout None std defaults: 0 "" None (false, '\0') -- custom Default -- Server { host: "0.0.0.0", port: 8443, workers: 4 } 0.0.0.0 8443 4 mode Fast safe? false empty map {} -- derive -- Version { major: 1, minor: 2 } == Version { major: 1, minor: 2 }? true a < newer? true max Version { major: 1, minor: 10 } set len 1 default Version { major: 0, minor: 0 }
Full program
// 21. Display, Default, and derive
//
// Display is the user-facing format used by {}. Implement it with write!(f, "...").
// Every Display type gets to_string() for free through ToString, so never implement
// ToString yourself. Display has no derive: you decide how a value reads to a user.
//
// Default::default() builds a reasonable empty value. #[derive(Default)] works when
// every field has a Default. Combined with struct update syntax it gives you
// "defaults, except these fields".
//
// #[derive(...)] asks the compiler to write impls for you. The built-in derives are
// Clone, Copy, Debug, Default, PartialEq, Eq, PartialOrd, Ord, and Hash. A derive fails
// if a field does not implement the trait.
//
// Run: cargo run --bin 21_display_default_derive
use std::fmt;
struct Meter(i32);
impl fmt::Display for Meter {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}m", self.0)
}
}
// Example 1: a minimal Display.
fn demo_display() {
println!("-- Display --");
let m = Meter(12);
println!("{m}");
let s: String = m.to_string();
println!("to_string {s} len {}", s.len());
// println!("{:?}", m); // error[E0277]: `Meter` doesn't implement `Debug`
}
struct Money {
cents: i64,
}
impl fmt::Display for Money {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let sign = if self.cents < 0 { "-" } else { "" };
let abs = self.cents.abs();
let text = format!("{sign}${}.{:02}", abs / 100, abs % 100);
// pad respects width and alignment from the caller's format spec.
f.pad(&text)
}
}
// Example 2: Display that honors the caller's width and alignment.
fn demo_display_padding() {
println!("-- Display with padding --");
let items = [("coffee", Money { cents: 450 }), ("refund", Money { cents: -1299 })];
for (name, price) in &items {
println!("[{name:<8}] [{price:>10}]");
}
}
#[derive(Debug)]
struct Matrix([[i32; 2]; 2]);
impl fmt::Display for Matrix {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
for row in &self.0 {
writeln!(f, "| {:>3} {:>3} |", row[0], row[1])?;
}
Ok(())
}
}
// Example 3: multi-line Display, and Debug next to it.
fn demo_display_vs_debug() {
println!("-- Display vs Debug --");
let m = Matrix([[1, 20], [300, 4]]);
print!("{m}");
println!("{m:?}");
// The alternate flag {:#} is available inside fmt as f.alternate().
}
#[derive(Debug, Default)]
struct Cfg {
host: String,
port: u16,
verbose: bool,
tags: Vec<String>,
timeout: Option<u32>,
}
// Example 4: derived Default.
fn demo_default() {
println!("-- Default --");
let d = Cfg::default();
println!("{d:?}");
let c = Cfg { port: 8080, ..Default::default() };
println!("{c:?}");
println!(
"host {:?} port {} verbose {} tags {} timeout {:?}",
c.host,
c.port,
c.verbose,
c.tags.len(),
c.timeout
);
println!(
"std defaults: {:?} {:?} {:?} {:?}",
i32::default(),
String::default(),
<Option<u8>>::default(),
<(bool, char)>::default()
);
}
#[derive(Debug)]
struct Server {
host: String,
port: u16,
workers: usize,
}
impl Default for Server {
fn default() -> Self {
Server { host: "0.0.0.0".into(), port: 80, workers: 4 }
}
}
#[derive(Debug, Default, PartialEq)]
enum Mode {
#[default]
Fast,
Safe,
}
// Example 5: hand-written Default, and #[default] on an enum variant.
fn demo_custom_default() {
println!("-- custom Default --");
let s = Server { port: 8443, ..Default::default() };
println!("{s:?}");
println!("{} {} {}", s.host, s.port, s.workers);
println!("mode {:?} safe? {}", Mode::default(), Mode::default() == Mode::Safe);
let counts: std::collections::HashMap<&str, i32> = Default::default();
println!("empty map {counts:?}");
}
#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Default)]
struct Version {
major: u32,
minor: u32,
}
// Example 6: what derive generates.
fn demo_derive() {
println!("-- derive --");
let a = Version { major: 1, minor: 2 };
let b = a.clone();
println!("{:?} == {:?}? {}", a, b, a == b);
let newer = Version { major: 1, minor: 10 };
// Derived ordering compares fields top to bottom.
println!("a < newer? {} max {:?}", a < newer, std::cmp::max(&a, &newer));
let mut set = std::collections::HashSet::new();
set.insert(a);
set.insert(b);
println!("set len {} default {:?}", set.len(), Version::default());
// #[derive(Clone)] struct NoClone { f: std::sync::MutexGuard<'static, ()> }
// error[E0277]: the trait bound `std::sync::MutexGuard<'_, ()>: Clone` is not satisfied
}
fn main() {
demo_display();
demo_display_padding();
demo_display_vs_debug();
demo_default();
demo_custom_default();
demo_derive();
}