cafca
cafca

Reputation: 1

What should an idiomatic `Display` implementation write?

The trait documentation says

Display is similar to Debug, but Display is for user-facing output, and so cannot be derived.

But what does that mean? Should it write the full string-encoded value even if that results in a 500 character output? Should it make a nice and friendly representation suitable for display in a user interface even if that results in to_string() not actually returning the full value as a string?

Let me illustrate:

Say I have a type that represents important data in my application. This data has a canonical string-encoding with a very specific format.

pub struct BusinessObject {
    pub name: String,
    pub reference: u32,
}

First, I want to implement Display so I can use it for making easily readable log messages:

impl Display for BusinessObject {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        // Formats like `Shampoglak (7384)`
        write!(f, "{} ({})", self.name, self.reference)
    }
}

Now, let's implement a method that returns the canonical standard string format for BusinessObject instances. As the as_str() method name is idiomatically only used when returning a string slice and that is not possible in this case, one could think that the most straightforward approach would be to implement a method to_string() for this.

impl BusinessObject {
    fn to_string(&self) -> String {
        // Formats like `Shampoglak00007384`
        format!("{}{:0>8}", self.name, self.reference)
    }
}

But no! This method name is already implemented as part of the automatic ToString trait implementation that we have because we implemented Display.

What does an idiomatic implementation of Display write? A full representation of a value as a string or a friendly, human-readable representation of it? How should I structure my code and name my methods if I need to implement both of those? I am specifically looking for a solution that can be applied generically and not just in this specific situation. I don't want to have to look up what the behavior of to_string() for a given struct is before I use it.

I didn't find anything in the documentation of associated traits and various Rust books and resources I looked into.

Upvotes: 0

Views: 121

Answers (2)

Kevin Reid
Kevin Reid

Reputation: 43842

What does an idiomatic implementation of Display write?

It writes the obvious string form of that data. What exactly that will be depends on the data, and it may not exist for some types.

Such a string representation is not necessarily “human friendly”. For example, the Display implementation of serde_json::Value (a type which represents arbitrary JSON data structures) produces the JSON text corresponding to the value, by default without any whitespace for readability — because that's the conventional string representation.

Display should be implemented for types where you can say there is “the string representation of the data” — where there is one obvious choice. In my opinion, it should not be implemented for types where there isn't one obvious choice — instead, the API should omit it, in order to guide users of the type to think about which representation they want, rather than giving them a potentially bad default.

A full representation of a value as a string or a friendly, human-readable representation of it?

In my opinion, a Display implementation which truncates the data is incorrect. Display is for the string form of the data, not a name or snippet of the data.

How should I structure my code and name my methods if I need to implement both of those?

For convenient use in format strings, you can write one or more methods which return wrapper types that implements Display (like Path::display() in std).

Upvotes: 0

eggyal
eggyal

Reputation: 125925

What does an idiomatic implementation of Display write? A full representation of a value as a string or a friendly, human-readable representation of it?

The latter: Display should produce a friendly, human-readable representation.

How should I structure my code and name my methods if I need to implement both of those?

"Full representations" of values as strings would more correctly be known as a string serialisation of the value. A method fn into_serialised_string(self) -> String would be one approach, but perhaps you want to consider a serialisation library like serde that separates the process of serialising (and deserialising) from the serialised format?

Upvotes: 1

Related Questions