Trust me, this isn't an abuse of Display

I don't mean that using Display
to print your desired format is an abuse. I meant that one shouldn't take expectations from Debug
(that recursive printing of a struct with fields can work by default) and apply them to Display
.
Besides, whether or not one traverses the struct in Display fully depends on what the struct data represents, as well as on how one wants to represent it to the user.
Yes. So. Let's talk about that. Let me frame it like this:
You want to print a certain data structure in a certain format, using the trait system. In order for the compiler to know the format you want, it must be specified to it via either the data type, or the trait.
- Specifying it via the data type looks like
impl Display for MyAppStruct
or impl Display for MyWrapper
— defining types that know how they are to be displayed.
- Specifying it via the trait looks like
impl LowerHex for MyAppStruct
— picking a trait that means the format you want.
When you Display
a ()
, there's no room to specify the format because neither the type nor the trait are yours, and as already discussed, ()
hasn't got a canonical display format (I am not going to get into that argument again). So, by the logic of Rust's trait system, you have to add something to convey your desired format. Otherwise the compiler can't know what you want.
If you want, you can do this and still use Display
everywhere, using a wrapper type:
use std::fmt;
/// Generic “display this *my* way” wrapper.
struct Dw<'a, T>(&'a T);
/// This is your generic struct that might contain an `()`
struct MyAppStruct<T> {
left: T,
right: T,
}
/// This is a mostly ordinary `Display` implementation
impl<T> fmt::Display for MyAppStruct<T>
where
for<'a> Dw<'a, T>: fmt::Display,
{
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let Self { left, right } = self;
write!(
f,
"On the left we have {}, and on the right we have {}.",
Dw(left),
Dw(right),
)
}
}
// Now we define how we want various primitives to be formatted for our application.
impl fmt::Display for Dw<'_, ()> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str("nothing")
}
}
impl fmt::Display for Dw<'_, bool> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str(if *self.0 { "truth" } else { "falsity" })
}
}
This is a simpler, less generic version of the technique manyfmt
uses.