RFC 0565: show-string-guidelines

lang (fmt)

Summary

A recent RFC split what was previously fmt::Show into two traits, fmt::Show and fmt::String, with format specifiers {:?} and {} respectively.

That RFC did not, however, establish complete conventions for when to implement which of the traits, nor what is expected from the output. That's what this RFC seeks to do.

It turns out that, due to the suggested conventions and other concerns, renaming the traits is also desirable.

Motivation

Part of the reason for splitting up Show in the first place was some tension around the various use cases it was trying to cover, and the fact that it could not cover them all simultaneously. Now that the trait has been split, this RFC aims to provide clearer guidelines about their use.

Detailed design

The design of the conventions stems from two basic desires:

  1. It should be easy to generate a debugging representation of essentially any type.

  2. It should be possible to create user-facing text output via convenient interpolation.

Part of the premise behind (2) is that user-facing output cannot automatically be "composed" from smaller pieces of user-facing output (via, say, #[derive]). Most of the time when you're preparing text for a user consumption, the output needs to be quite tailored, and interpolation via format is a good tool for that job.

As part of the conventions being laid out here, the RFC proposes to:

  1. Rename fmt::Show to fmt::Debug, and
  2. Rename fmt::String to fmt::Display.

Debugging: fmt::Debug

The fmt::Debug trait is intended for debugging. It should:

In terms of the output produced, the goal is make it easy to make sense of compound data of various kinds without overwhelming debugging output with every last bit of type information -- most of which is readily available from the source. The following rules give rough guidance:

It is not a requirement for the debugging output to be valid Rust source. This is in general not possible in the presence of private fields and other abstractions. However, when it is feasible to do so, debugging output should match Rust syntax; doing so makes it easier to copy debug output into unit tests, for example.

User-facing: fmt::Display

The fmt::Display trait is intended for user-facing output. It should:

A common pattern for fmt::Display is to provide simple "adapters", which are types wrapping another type for the sole purpose of formatting in a certain style or context. For example:

pub struct ForHtml<'a, T>(&'a T);
pub struct ForCli<'a, T>(&'a T);

impl MyInterestingType {
    fn for_html(&self) -> ForHtml<MyInterestingType> { ForHtml(self) }
    fn for_cli(&self) -> ForCli<MyInterestingType> { ForCli(self) }
}

impl<'a> fmt::Display for ForHtml<'a, MyInterestingType> { ... }
impl<'a> fmt::Display for ForCli<'a, MyInterestingType> { ... }

Rationale for format specifiers

Given the above conventions, it should be clear that fmt::Debug is much more commonly implemented on types than fmt::Display. Why, then, use {} for fmt::Display and {:?} for fmt::Debug? Aren't those the wrong defaults?

There are two main reasons for this choice:

In other words, although more types implement fmt::Debug, most meaningful uses of interpolation (other than in such implementations) will use fmt::Display, making {} the right choice.

Use in errors

Right now, the (unstable) Error trait comes equipped with a description method yielding an Option<String>. This RFC proposes to drop this method an instead inherit from fmt::Display. It likewise proposes to make unwrap in Result depend and use fmt::Display rather than fmt::Debug.

The reason in both cases is the same: although errors are often thought of in terms of debugging, the messages they result in are often presented directly to the user and should thus be tailored. Tying them to fmt::Display makes it easier to remember and add such tailoring, and less likely to spew a lot of unwanted internal representation.

Alternatives

We've already explored an alternative where Show tries to play both of the roles above, and found it to be problematic. There may, however, be alternative conventions for a multi-trait world. The RFC author hopes this will emerge from the discussion thread.

Unresolved questions

(Previous questions here have been resolved in an RFC update).