We can't find the internet
Attempting to reconnect
Something went wrong!
Attempting to reconnect
← Generics and Traits step 6 of 24
Display, Debug, and the ToString blanket impl
Rust has two formatting traits and they are for two different audiences.
-
Debug—{:?}— is for programmers. Derive it (#[derive(Debug)]) on almost everything. Output is allowed to be ugly and to change. -
Display—{}— is for users. You must write it by hand, because only you know what “showing this to a human” means.
This exercise is about Display, and about a piece of std machinery that
catches people out the first time.
The blanket impl
The standard library contains, roughly:
impl<T: Display + ?Sized> ToString for T {
fn to_string(&self) -> String { /* format into a String */ }
}
That is a blanket impl: one impl covering every type at once, conditional
on a bound. The consequence is immediate and load-bearing —
implementing Display gives you .to_string() for free. You must never
implement ToString yourself, and you must never add an inherent
fn to_string(&self) to your type either.
Three separate default-on lints police exactly this boundary:
-
clippy::to_string_trait_impl— you wroteimpl ToString for Money -
clippy::inherent_to_string— you wroteimpl Money { fn to_string(&self) } -
clippy::inherent_to_string_shadow_display— correctness, deny — you did that and also implementedDisplay, so which one runs now depends on method resolution rather than on what you meant
The starter walks straight into the first one. Read the error, then move the logic where std wants it.
A correctness lint that will save you
clippy::recursive_format_impl is deny-by-default and catches this:
impl Display for Money {
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
write!(f, "{}", self) // infinite recursion, stack overflow
}
}
Beginners write that constantly — self is right there and {} is the thing
you are implementing. Format the fields, never self.
Your task
pub struct Money { pub cents: i64 }
impl Display for Money { ... }
pub fn render(cents: Vec<i64>) -> Vec<String>
Render an amount in cents as currency, thousands-grouped:
| cents | output |
|---|---|
123456 |
$1,234.56` |
| `0` | `$0.00 |
5 |
$0.05` |
| `-123456` | `-$1,234.56 |
100000000 |
$1,000,000.00` |
| `i64::MIN` | `-$92,233,720,368,547,758.08 |
The sign goes outside the dollar mark. The fractional part is always two
digits — {:02} in a format string. render must produce its strings by
calling .to_string(), which is your proof the blanket impl fired.
Watch the negative-numbers edge. -self.cents overflows for i64::MIN, and
under -O there are no overflow checks, so it would silently produce
i64::MIN again and print the wrong sign. i64::unsigned_abs returns a u64
and is the correct tool.
One formatting fact
Formatter makes no newline guarantee and adds nothing of its own. What
you write is exactly what comes out, which is why the expected strings here
contain no whitespace you did not put there. write!(f, ...) returns
fmt::Result; propagate it with ? and return the last one.
Remember the grade is compile + tests + clippy -D warnings.
Stuck?
Rust reference solution
Sign in to attempt this problem and reveal the reference solution.