The standard library defines the 3 traits which are related to each other, but, in my opinion, it's not clear enough when the traits should be and should not be implemented. Below I will try to describe my understanding of those traits. I am interested to hear whether everyone agrees with it and, if not, to hear alternative interpretations.
Let's start with ToString, its docs are quite clear:
ToStringshouldn’t be implemented directly:Displayshould be implemented instead
This gets enforced by the new to_string_trait_impl Clippy lint. So ToString is essentially a convenience proxy for Display.
Next, let's consider the FromStr trait. Its main use is the std::parse method. It's not stated explicitly in the docs, but I think it's implied that FromStr should be symmetric to ToString. In other words, FromStr::from_str(&ToString::to_string(&val)).unwrap() should produce the same value as in val. Because of its relation to ToString and subsequently to Display, it looks like the main use of the trait is to handle user-provided input, i.e. data which users may manually input.
Finally, the Display trait. The docs state the following:
Displayis for user-facing output
fmt::Displayimplementations assert that the type can be faithfully represented as a UTF-8 string at all times. It is not expected that all types implement the Display trait.
I have the following personal conditions for having a Display implementation:
- It should be intended for direct user consumption.
- It should be stable and unambiguous.
- It should be usable with non-trivial (i.e. non-
"{}") formatting strings.
The later condition means that an implementation should be "simple enough", i.e. collections (e.g. Vec) should not implement Display because formatting result may be huge.
The main reason for creating this post is this PR. The elliptic-curve crate currently has ToString and FromStr (but not Display) implementations for PublicKey and SecretKey types which are based on the PEM encoding. The implementations are used as convenient and easily discoverable serialization/deserialization APIs.
In my opinion, it's a misuse of the APIs, since PEM encoded data is not intended for direct human consumption (it contains a human readable header, but otherwise it contains unintelligible base64-encoded bytes) and users certainly will not manually provide such data. But @bascule would prefer to keep the implementations for convenience sake.
A more ambiguous example is Display and FromStr implementations for Scalar and NonZeroScalar types (essentially, bounded stack-based bigints). The implementations are based on hex encoding. It works fine, but I don't think that Display and FromStr should be implemented since (application) users should never deal with these types directly. Instead I think we should use Debug, LowerHex, and UpperHex formatting traits and inherent serialization/deserialization method to/from hex encoding.
What do you think? What criteria do you use in your code?