Formatting Traits (std::fmt)
Formatting Traits (std::fmt)
Level 4 — Rust Traits in
std::fmt—Display,Debug,LowerHex,Binary, etc. — providing structured text formatting via{}and{:?}placeholders.
1. Prerequisites
DisplayTrait — The Display trait for user-facing formatting.DebugTrait — The Debug trait for developer-facing formatting.
2. Term Category
Rust Standard Library (custom print formatting traits): Display (user-facing {}) and Debug (developer-facing {:?}) formatting traits.
3. Explanation
(1) Design Motivation — "Why did we design this?"
Unstructured print statements make debugging and error reporting inconsistent.
Rust categorizes text formatting into Display (user-facing output formatted via {}) and Debug (developer-facing structural inspection formatted via {:?}). Structs can derive #[derive(Debug)] automatically, while implementing Display specifies custom public formatting.
(2) Reality Metaphor
A museum display plaque vs an engineering blueprint: the display plaque (Display) presents a clean title for museum visitors; the engineering blueprint (Debug) shows exact internal measurements and structural components for maintenance technicians.
(3) Rust Code Examples
Short Snippet
use std::fmt;
#[derive(Debug)] struct Point { x: i32, y: i32 }
println!("{:?}", Point { x: 1, y: 2 });
Fuller Example
use std::fmt;
pub struct Money {
pub cents: i64,
}
impl fmt::Display for Money {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "${:.2}", (self.cents as f64) / 100.0)
}
}
fn main() {
let m = Money { cents: 1250 };
assert_eq!(format!("{}", m), "$12.50");
}
4. Common Mistakes & Pitfalls
Mistake 1: Attempting to Print Structs with {} Without Implementing Display
The mistake: Using {} format placeholder on a struct that only derives #[derive(Debug)].
Why it is wrong: {} requires explicit implementation of std::fmt::Display. Use {:?} for Debug formatting.
Incorrect:
#[derive(Debug)] struct User { id: u64 } println!("{}", user); // Error!
Fix:
println!("{:?}", user); // Use {:?} for Debug!
Mistake 2: Writing Recursive Display Implementations Causing Stack Overflow
The mistake: Invoking write!(f, "{}", self) inside Display::fmt.
Why it is wrong: Recursively calls Display::fmt on self, causing a stack overflow crash.
Incorrect:
impl fmt::Display for Item { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { write!(f, "{}", self) } }
Fix:
Format inner fields: write!(f, "{}", self.name)
Mistake 3: Using Display for Internal Debugging Information
The mistake: Including private implementation details or memory addresses inside Display implementations.
Why it is wrong: Display is for clean end-user presentation. Put structural details in Debug ({:?}).
Incorrect:
impl fmt::Display for Node { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { write!(f, "Node(ptr={:p})", self) } }
Fix:
Use #[derive(Debug)] for structural metadata!
5. Practice Exercises
Exercise 1: Custom Currency Formatter with Display and Debug
Scenario: Build a custom financial currency struct Currency implementing Display (formatting as $1,234.50) and Debug (showing internal integer cents).
Requirements:
- Define
Currencywithcents: i64. - Implement
Displayformatting as$D.CC. - Derive
Debug. - Write unit tests.
Answer
Implementation
use std::fmt;
#[derive(Debug, PartialEq)]
pub struct Currency {
pub cents: i64,
}
impl Currency {
pub fn from_dollars(dollars: f64) -> Self {
Self { cents: (dollars * 100.0) as i64 }
}
}
impl fmt::Display for Currency {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let dollars = self.cents / 100;
let remaining_cents = (self.cents % 100).abs();
write!(f, "${}.{:02}", dollars, remaining_cents)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_currency_formatting() {
let c = Currency::from_dollars(49.99);
assert_eq!(format!("{}", c), "$49.99");
assert_eq!(format!("{:?}", c), "Currency { cents: 4999 }");
}
}
Technical Explanation
Displayimplementation formatsCurrencycleanly as$49.99for end users.- Derived
Debugshows internal struct layoutCurrency { cents: 4999 }for developers.
Exercise 2: Pretty-Printed Hex Byte Array Debug Formatter
Scenario: Implement custom LowerHex formatting for a byte array struct MacAddress.
Requirements:
- Define
MacAddress([u8; 6]). - Implement
fmt::Displayformatting as colon-separated hex.
Answer
Implementation
use std::fmt;
pub struct MacAddress(pub [u8; 6]);
impl fmt::Display for MacAddress {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(
f,
"{:02X}:{:02X}:{:02X}:{:02X}:{:02X}:{:02X}",
self.0[0], self.0[1], self.0[2], self.0[3], self.0[4], self.0[5]
)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_mac_display() {
let mac = MacAddress([0x00, 0x1A, 0x2B, 0x3C, 0x4D, 0x5E]);
assert_eq!(format!("{}", mac), "00:1A:2B:3C:4D:5E");
}
}
Technical Explanation
- Custom
Displayformats MAC address bytes into upper-case colon-separated hex representation.
Exercise 3: Custom Struct Pretty Debug Formatter f.debug_struct()
Scenario: Implement custom Debug manually for a User struct hiding sensitive password fields.
Requirements:
- Implement
Debugusingf.debug_struct()masking password.
Answer
Implementation
use std::fmt;
pub struct UserAccount {
pub username: String,
pub password_hash: String,
}
impl fmt::Debug for UserAccount {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("UserAccount")
.field("username", &self.username)
.field("password_hash", &"[REDACTED]")
.finish()
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_redacted_debug() {
let u = UserAccount { username: "alice".into(), password_hash: "secret123".into() };
let dbg = format!("{:?}", u);
assert!(dbg.contains("[REDACTED]"));
assert!(!dbg.contains("secret123"));
}
}
Technical Explanation
f.debug_struct()allows building custom developer debug outputs that redact sensitive fields.
6. Related Terms
- None!
7. Key Takeaways
Display({}) is for clean user-facing output.Debug({:?}) is for developer structural inspection.- Derive
#[derive(Debug)]automatically for structs. - Use
f.debug_struct()to redact sensitive fields in customDebugimpls.