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:
ToString
shouldn’t be implemented directly:Display
should 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:
Display
is for user-facing output
fmt::Display
implementations 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?