I'm often bothered by:
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str("Hello, ")?;
self.value.fmt(f)?;
f.write_str("!")?;
Ok(())
}
not being writeable with our nice write!() sugar.
That is:
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "Hello, {}!", self.value)?;
Ok(())
}
is not equivalent to the previous snippet, but rather, to:
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str("Hello, ")?;
f.write_fmt(format_args!("{}", self.value))?;
f.write_str("!")?;
Ok(())
}
The difference?
The keen eye will already be suspecting something, insofar my fn fmt snippet is on purpose not specifying whether that snippet was occurring in a Display or Debug impl context, whereas our write!() impl already had to "hard-code"/specify one ({} or {:?}).
self.value.fmt(f) // Display or Debug
// vs.
f.write_fmt(format_args!("{}", self.value)) // or we could be using `{:?}`
More generally, by spelling out {} (or {:?}), we are "hard-coding"/specifying not only the formatting trait to be using, but also the rest of the formatting machinery "metadata"/specifiers, such as:
- expanded:
{:}/{:?}vs.{:#}/{:#?} - precision:
{:}/{:?}vs.{:.4}/{:.4?} - padding, etc.
Hence the need, sometimes, to actually go and write the ugly:
f.write_str("Hello, ")?;
self.value.fmt(f)?;
f.write_str("!")?;
-
Playground Demo
Click to see
struct A(i32); impl ::core::fmt::Display for A { fn fmt(&self, f: &mut ::core::fmt::Formatter<'_>) -> ::core::fmt::Result { f.write_str("Hi ")?; self.0.fmt(f)?; f.write_str("!")?; Ok(()) } } struct B(i32); impl ::core::fmt::Display for B { fn fmt(&self, f: &mut ::core::fmt::Formatter<'_>) -> ::core::fmt::Result { write!(f, "Hi {}!", self.0)?; Ok(()) } } fn main() { let a = A(42); let b = B(42); assert_eq!( format!("{a:04} {b:04}"), "Hi 0042! Hi 42!", ); }
My point
Is not to necessarily come up with the One True Solution™, but rather, to kickstart a discussion about potential venues to solve this.
My humble suggestion
would be to add some new sigil formatting specifier; off the top of my head, something like {:!} or {:_} or {:...}.
- it could result in a special
Placeholdermarker; - which would then be inspected within
<impl fmt::Write for fmt::Formatter<'_>>::write_fmt()'s body.
EDIT: looking at that body, it feels like lack of Placeholder specifiers is already technically possible from the PoV of these callees, which do handle the stuff exactly as I had envisioned it. However, I do not know how to produce such a thing other than manually constructing a fmt::Arguments<'_> instance, which is gated as unstable and internal
A potential follow-up: deep vs. shallow Error : Display specifiers
Once we had this solved, we could finally put an end to the Error : Display convention-deciding debate (as to whether .source()s ought to be displayed as well; or left for the caller to manually walk up the .source() chain), by deciding that:
{:#}would display the source error info as well;{}would not.- (Or the other way around.)
That way error-chain-printing frameworks could still control this rather nicely (by using the shallow specifier), whilst still allowing a simple/naïve print to give as much info as possible (by using the deep specifier, probably best reserved to the naïve {}), without requiring that everybody implement an error-chain-walker.