Typical implementations of formatting specifiers for Display

Hello! I've just started my first Rust project (a Project Euler conversion), and I'm not understanding typical use of the fmt::Display trait.

Typical implementations of the trait seems be over-complicated ToStrings. In particular, example Errors all write fixed strings, ignoring format specifiers. At first I assumed that write!(f, "{}", msg) would apply the format specifiers from f, sort of like how #[derive(Debug)] formats values. It does not seem to.

See https://play.rust-lang.org/?version=stable&mode=debug&edition=2018&gist=4c6e4999dfd8bc82529b9b514897d604.

I guess I'm left with these questions:

  • Why does write!(f, ...) not forward the format specifiers from f?
  • Why is typical to ignore format specifiers?
  • If it's unreliable to use format specifiers, why not a separate trait for that?

I'm also unsure whether the format specification is expected to change over time. If I properly implement formatting specifiers, will my implementation become less-than-fully-correct in the future? (Affects my project because my big decimal cannot use pad_integral() and also avoid allocating.)

I imagine this would be complicated to implement or use, because you'd have one set of formatting specifiers from f and one from the format string passed to write!, and it's not obvious how to combine them. And some parameters, like width, don't make sense to forward to the fields of a compound type.

Maybe there should be a special format string that means "inherit the format specifier from the caller."

I think it's just because it's simple and easy to ignore them. Implementing formatting options is more work, often with questionable benefit and uncertain "correct" behavior. (E.g., should all the fields in my struct be printable in binary and hexadecimal, or just the one that represents a checksum?)

It was never expected that all format specifiers apply to all types. (For example, some of them only make sense for floating-point values.) Instead, they are optional things that types can implement where there is user demand for it.


FWIW, if you are interested in delegating to the Display of the wrappee, you may do so by using a direct call to the inner Display::fmt() implementation:

- write!(f, "{}", self.0)
+ path::to::Display::fmt(&self.0, f)

Obviously this becomes harder and more cumbersome to do the moment the format string becomes something more complicated than "{}" :sweat_smile:


self.0.fmt(f) works too.

One trouble with .fmt(), in some cases, is the conflict between Debug::fmt and Display::fmt.

1 Like