Step 2 of 7
Display: messages for people
{:?} prints InsufficientFunds { balance: 70, requested: 500 }. That's useful in a log for developers, but a customer should see insufficient funds: balance 70, requested 500. The {} placeholder uses a different format, called Display, and for your own types you write it yourself (you did it for Money in module 18). Error types work the same way.
Display is a trait: a named set of methods that a type promises to have (module 22 covers traits properly). You make {} work for your type by implementing the Display trait for it. The recipe always has the same shape:
use std::fmt;
#[derive(Debug)]
enum WithdrawError {
InvalidAmount,
InsufficientFunds { balance: u64, requested: u64 },
AccountFrozen,
}
impl fmt::Display for WithdrawError {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match self {
WithdrawError::InvalidAmount => write!(f, "amount must be greater than zero"),
WithdrawError::InsufficientFunds { balance, requested } => {
write!(f, "insufficient funds: balance {balance}, requested {requested}")
}
WithdrawError::AccountFrozen => write!(f, "account is frozen"),
}
}
}
fn main() {
let errors = [
WithdrawError::InvalidAmount,
WithdrawError::InsufficientFunds { balance: 70, requested: 500 },
WithdrawError::AccountFrozen,
];
for e in &errors {
println!("declined: {e}");
}
let message = errors[2].to_string();
println!("{message:?} has {} characters", message.len());
println!("{:?}", errors[0]);
}
declined: amount must be greater than zero
declined: insufficient funds: balance 70, requested 500
declined: account is frozen
"account is frozen" has 17 characters
InvalidAmount
Piece by piece:
use std::fmt;brings the formatting module into scope, so you can writefmt::Displayinstead ofstd::fmt::Display.impl fmt::Display for WithdrawError { ... }reads "here's how aWithdrawErroris displayed".fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Resultis the one method Display requires. The signature is fixed by the trait: copy it exactly. (Module 18 and the standard docs writefmt::Formatter<'_>; the'_is a lifetime placeholder, which module 24 explains. Both forms compile and mean the same thing.)write!(f, ...)works likeformat!, but writes the text into the formatterf. It returns afmt::Result, so it's the value of eachmatcharm, and of the whole method.- Once Display exists,
{},format!and.to_string()all work.{:?}still uses the derived Debug.
Workplace note: the standard library's documentation for errors recommends short, lowercase messages without a trailing period, like its own invalid digit found in string. Messages often get embedded in longer ones (error: could not load config: invalid digit found in string), and that reads well only if each piece is a lowercase fragment.
Common mistakes
- Printing with
{}before implementing Display:
#[derive(Debug)]
enum WithdrawError {
AccountFrozen,
}
fn main() {
let e = WithdrawError::AccountFrozen;
println!("declined: {e}");
}
error[E0277]: `WithdrawError` doesn't implement `std::fmt::Display`
- A
;afterwrite!in a match arm. The arm must return thefmt::Result. In an arm without braces the;is a syntax error (`match` arm body without braces); inside braces,{ write!(...); }returns()and you getmismatched types. - Using
println!insidefmt. That prints to the screen right away instead of producing the text. Alwayswrite!(f, ...). - Forgetting
use std::fmt;givescannot find module or crate `fmt`. Either add theuseor write the full pathstd::fmt::Display.
Your task
Implement Display for TicketError so that:
SoldOutdisplays asthe event is sold outTooMany { requested: 9, max: 6 }displays ascannot buy 9 tickets, the limit is 6UnknownEvent("Rust Fest")displays asno event called 'Rust Fest'
Challenge 2
Give UsernameError (from the last step) a Display implementation:
Empty:username cannot be emptyTooLong(20):username has 20 characters, the maximum is 16InvalidChar(' '):username cannot contain ' '
Challenge 3
Write a grading function with its own error type, from scratch.
GradeError(deriveDebugandPartialEq) has the variantsNegative(i32)andAboveMax(i32). Its Display messages arescore cannot be negative: -5andscore 105 is above the maximum of 100.fn letter_grade(score: i32) -> Result<char, GradeError>: a score below 0 or above 100 is an error; otherwise 90 and up is'A', 80 and up'B', 70 and up'C', 60 and up'D', anything lower'F'.
Previous: An enum for everything that can go wrong Next: The Error trait and Box<dyn Error>