Should Vec (and other contains) implement Display?

I'd like to suggest Vec (and other standard containers) should implement Display (unless there is a good technical reason not to).

I realise this is probably an old discussion, but I couldn't actually find much about it, so I wanted to raise it. If this has been done to death, and there are good reasons not to do it, let me know and we can always close this thread early :slight_smile:

I think I know the main argument against, that there isn't "one best way" to do display. Except, I feel there is a very reasonable way of doing 'Vec', the same way they are inputted into Rust, comma seperated with : [1, 2, 3] for example.

Will this be what everyone wants? Probably not, but they don't have to use it if they don't want it. I'm not clear why 'nothing' is better than 'something some people won't want to use?'

On the other hand, while I've been teaching Rust, I've found this a pain point a couple of times, people learn about printing, learn about Vec, and then the two don't go together. You can debug print, but then you also debug print the things inside the Vec.

I'm open to suggestions on how to nicely Display the members of a Vec, but ideally it would be nice if they were suitable for students in their first week or two of Rust.

One of the reasons not to do recursive Display is that it’s misleading for vectors like ["a, b, c", "x, y, z"]. A bit of an edge case, sure, but especially if someone is using Display for structured textual data, it could have real consequences.

11 Likes

That is a good example of how something could go wrong

I will make a PR to 'Rust by example', which is what came up first for me in google and bing when I searched for this issue, and add this example.

The current text there says people might have a prefered seperator (, or :), and I personally didn't find that so convincing.

1 Like

There isn't "one best way" to show containers to users. As programmers, we often want to see the contents of our data structures, but this is what the Debug trait is for, and it is implemented for containers with the expected formatting. Showing [a, b, c] to users is not the best UX, certainly not always. Sometimes it's lines separated, sometimes it's comma separated but without braces. So I argue that it is better to leave the situation as-is.

10 Likes

But, I feel I could make that argument more generally?

I personally think the default Display for f64 is bad. There are many opinions on how floats should be printed, so should there be a Display implementation for f64?

'true' and 'false' are not localised to user's languages, so that is not the best UX, so should there be a Display implementaiton for bool?

(Of course, I'm definately not suggesting these should be removed, they are clearly useful, and removing them would break huge amounts of code).

Similarly, the Rust team could (and I think should) decide there is a reasonable default for Vec -- No-one has to use it, but it's there for people who want to 'Display' the contents of their Vec.

6 Likes

It was RFC'd after debug-intended and user-intended traits were split up.

5 Likes

Thanks, I didn't find this in my search. It is good to see the discussion.

Another aspect of Display is the implied ToString implementation.

I cannot fully assess how much of a problem it is, but I can at least imagine possible confusion from converting String via into to a Vec, then back via to_string to a String, and getting a really different result.

fn main() {
    let s = "abc";

    let a = s.to_string();
    let b: Vec<_> = a.into();
    let c = b.to_string();

    println!("{s}"); // "abc"
    println!("{c}"); // "[97, 98, 99]"
}

On a similar note, there is b"abc" syntax for creating [u8; N], which can be possibly be confusing with Display, and there’s a whole crate to help treating Vec<u8> and [u8] a bit more like String and str (but without the strict valid-UTF-8 guarantees).

7 Likes

Shameless self-plug: check out my color-your-life crate. It uses Display-like machinery but allows for both formatting and styling.

No as strongly.

Yes, there are many options to display f64, but Rust has made the explicit decision that formatting is not dependent of localization. When not considering localization, there is exactly one correct way to print a float - and this is also useful, since many programs don't need localization anyway.

One can argue about a thousands separator (which I believe it's unfortunate that std does not provide) - but that is an option that both have reasonable usages, like precision and padding.

Indeed I think that bool implementing Display is somewhat unfortunate. Of course we cannot remove it now, however. Still, I believe true/false are more pervasively correct values for booleans (e.g. in serialization formats) than any sequence formatting.

Somewhat relevant, though the context is different in a number of ways: Down with Show! Part 2: What's wrong with the Show type class - Harry Garrood

1 Like

If there was only one correct way, that would have to be printing the exact decimal value, since that would be the only way that parsing the string into a float with more precision would preserve the value. I don't think that would be a good default either, but I'm not a fan of this in the current implementation:

let x = 2.0f32.powi(40);
let y = 2u64.pow(40);
assert_eq!(x, y as f32);
assert_eq!(x as u64, y);
assert_eq!(x.to_string(), "1099511600000");
assert_eq!(y.to_string(), "1099511627776");

Even though the float exactly represents the same number, the printed value is rounded, which can sometimes be quite confusing. There's more on that in f32 formatter produces strange results · Issue #63171 · rust-lang/rust · GitHub

4 Likes

Indeed, you should always use scientific notation! Wait what is that, you disagree? Maybe there are multiple ways then after all.

1 Like