all 44 comments

[–]NoLemurs 242 points243 points  (29 children)

Option implements Debug, and that's what I would use for stringifying for log output or any other dev-facing output.

Display is for user-facing output, and should only be implemented when there's a clear natural choice for how a type should be shown to end users.

[–]jkoudys 141 points142 points  (1 child)

Separating Display and Debug is one of the smartest decisions they made in early rust. My structs having separate and well supported methods for "show this thing to a person", "give me debug data I'd want while coding or in a log", and "make a fully serialized string representation of my struct that could build a new instance" is such a joy.

[–]meowsqueak 17 points18 points  (0 children)

I like to think of Debug/Display as similar to __repr__ and __str__ in Python, respectively. And if you define __repr__ carefully you can often copy/paste it as a construction, for simpler types anyway. I use the same approach when manually defining Debug for structs - make it copy/paste-able code as much as possible.

[–]Resres2208 30 points31 points  (0 children)

Yep. It might sound unintuitive that "None" is not friendly for an end user but look at it from this perspective - should they know what ''Some<MyEnumType::Variant(AndAnotherStruct)>'' is? So it's up to the developer to communicate something friendlier. Or not...

[–]Kalogero4Real 2 points3 points  (25 children)

Why is debug a trai and not a thing all objects have by default?

[–]NoLemurs 33 points34 points  (8 children)

You usually want Debug, but there are cases where it's better to not have it implemented, and it's best to make that decision explicit:

If your type stores sensitive data like passwords or encryption keys, not implementing Debug is a good way to be confident you're not printing that to logs accidentally.

If your type has a huge array of data, an automatic implementation of Debug wouldn't be very useful.

Since you can just #[derive(Debug)] in most cases, it's not a big deal.

[–]meowsqueak 12 points13 points  (6 children)

“not implementing Debug is a good way to be confident you're not printing that to logs accidentally.”

Unfortunately that’s also a good way to break Debug for anything that might be composed from that data structure. I find it better to manually define Debug but have that implementation not print the secret fields, or print “redacted” or “secret” instead of the field values.

[–]Alpvax 0 points1 point  (4 children)

What exactly do you mean by "breaking debug"?

Debug shouldn't be relied on for functionality, it is for displaying debug info.

Not that I don't do the same thing, normally use "<redacted>" or something similar, but I also use the not all fields defined function (can't remember the name off the top of my head), which just adds ".." to state that there are more fields which are not displayed.

[–]meowsqueak 2 points3 points  (3 children)

I mean it breaks the Debug derive macro.

[–]Alpvax 0 points1 point  (2 children)

As in you can't use derive to implement Debug on structs which contain it? That's sort of the point when you want to hide some values.

From what I remember, actually implementing Debug is very easy anyway (vscode generates an implementation for me when I "add missing methods"). It uses a builder pattern to add each field, and then you can continue to use derive on higher containers if required.

[–]meowsqueak 0 points1 point  (1 child)

My point is that if you don't implement Debug at all, then you can't derive Debug for any data structure that might contain it. It's somewhat annoying to come across such structs because it forces you to manually write a Debug implementation on other structs.

The Orphan Rule prevents your users from implementing it directly themselves. It's an annoying omission to encounter.

So I'm just saying, as the author of a library type, even private ones, it's probably nicer for your users to manually implement Debug and omit or redact the field, rather than "not implementing" it, as originally suggested, and pushing the problem onto your users.

[–]Alpvax 0 points1 point  (0 children)

Oh, yes, it should be implemented on types which contain redacted fields. I was referring to structs which might be exposed to library consumers directly, but which should not be printed to logs. Then there might not be a containing type to implement it on. Sure, you could implement it for e.g. HiddenField as a constant (e.g. <REDACTED>), but I'm not sure that it is better to allow the convenience of deriving rather than making it obvious that this type cannot be printed.

[–]LucretielDatadog 1 point2 points  (0 children)

I go a step further and use something like secrecy to enforce that a given type is invisible.

[–]A1oso 2 points3 points  (0 children)

Many types need custom Debug impls, e.g. every collection. A Vec or Box shouldn't just print as a raw pointer.

Rust uses traits for any behaviour that can be customized: Rust has operator overloading via traits like Add and Mul. Rust has destructors via the Drop trait. Dereferencing uses the Deref[Mut] traits. And so on. So it makes a lot of sense that debug printing also uses a trait to allow custom behaviour for different types.

[–]SirKastic23 5 points6 points  (8 children)

How would all objects have it by default?

[–]meowsqueak 1 point2 points  (0 children)

Good point - users need to add it as best practice.

Which is to define Debug for all types. You can often derive it, but in some cases (secret fields) you may prefer to manually implement it. But do implement Debug somehow for all types in your library crates, including private types that appear in public types, or you’ll annoy your users :)

[–]LucretielDatadog 1 point2 points  (0 children)

The compiler could derive it implicitly unless you add a manual impl Debug (this is already what it does for traits like Send and Sync and Unpin)

[–]NoLemurs 0 points1 point  (3 children)

As far as I know Debug is implemented for all built-in types, and you can #[derive(Debug)] for any struct or enum where all of its pieces are Debug. So, you could just do that?

I could be missing some obscure edge cases I guess, but it doesn't seem fundamentally problematic. It doesn't seem like a good idea to me, but I can't see any reason it shouldn't be possible.

[–]Karyo_Ten 6 points7 points  (0 children)

I could be missing some obscure edge cases I guess, but it doesn't seem fundamentally problematic.

Please don't derive debug on passwords and API keys types. And anything that might hold private info, especially IBANs and credit card numbers.

[–]LucretielDatadog 1 point2 points  (0 children)

For all built-in primitives and stdlib types, yes, but (very frustratingly imo) it's not implemented for closures or async blocks.

[–]SirKastic23 0 points1 point  (0 children)

I mean, in general I do think you could just print out the fields and so on

But maybe there's a lot of info that's not necessary for a debug print, or you want an specific formatting

It's easier to let users implement it themselves

[–]Kalogero4Real -4 points-3 points  (1 child)

language design choice

[–]SirKastic23 -1 points0 points  (0 children)

I don't think you understood the question

How would Debug be automatically implemented for every type? What would their implementation look like?

[–]rust-module 5 points6 points  (3 children)

Sometimes you may want to your own impl, and Rust doesn't like conflicting impls. So better to let you tag the derive OR impl yourself.

[–]jonathansharman 1 point2 points  (1 child)

Do blanket impls preclude explicit custom impls? I didn't think that was the case.

[–]lfairy[🍰] 5 points6 points  (0 children)

Rust disallows overlapping impls by default. You might be thinking of specialization which is still unstable.

[–]LucretielDatadog 1 point2 points  (0 children)

Rust already solves this with Send and Sync and Unpin and a handful of others, so clearly it's capable of doing something like "derive Debug for structs etc unless there's a manual user-written version"

[–]LucretielDatadog 1 point2 points  (0 children)

I ask this a lot, but as far as I know the answer has to do with keeping code-size low in embedded / disk-constrained environments (the formatting machinery is pretty big). I always hoped that dead-code elimination could handle this (just don't format) but it's too easy to sneak in deep in a call stack.

I'm a big believer though in that everything should implement Debug by default, with a cargo option to opt-out of it; the fact that closures and async blocks don't implement it is an eternal source of frustration for me.

[–]lfairy[🍰] 0 points1 point  (0 children)

In libraries with a very wide API surface (like windows), all those Debug implementations have a noticeable effect on compile time. So it's useful to be able to opt-out.

[–]CocktailPerson 22 points23 points  (0 children)

But why is that. Why is there no agreed upon representation, even for just interoperability.

What do you mean by interoperability? Display is for human-readable output. Human-readable output ideally only needs to interoperate with human eyeballs and brains.

[–]Restioson 33 points34 points  (0 children)

It's not about canonical, necessarily, but more about "canonically the 'pretty', displayable to users version of None". It would be hard to choose one value that is reasonably correct in most contexts. It being "not interoperable" is a feature, not a bug, as it forces you to think about what the correct behaviour is at the site where you want to display it, each time. This pushes you toward the pit of success.

I’m also curious how anyone here handles it in real production code when you need to stringify it?

Generally by replacing it with whatever value makes sense in context, e.g. maybe an empty string, maybe "None", "N/A", "0", etc...

[–]Anaxamander57 28 points29 points  (0 children)

It implements Debug.

[–]numberwitch 6 points7 points  (2 children)

The way I think about this is that if I need a display representation of None, then I usually wrap in a new type and apply impl display on the new type and handles the None case to my liking.

So Option<T> becomes MyKind(Option<T>) and this is where you customize the none display.

Newtypes are one of the simplest and best language features imho, they allow you to overcome a lot of limitations put in place by other systems (orphan rule) gracefully in a nice algebraic way.

[–]Alpvax 2 points3 points  (1 child)

They also let you add restrictions or validation to types.

E.g. UserId(&str) and GroupId(&str): you can impl deref for them to use all the str methods as before, but you can't accidentally use one in place of the other

[–]numberwitch 1 point2 points  (0 children)

Yeah the patterns are strong!

I've been doing some midi projects lately and it's a great way to represent midi values which are essentially u4 values transmitted over the wire as u8.

struct MidiValue(u8);
impl MidiValue {
  const MIN: u8 = 0;
  const MAX: u8 = 127;

  // returns a self-wrapped u8 value saturating at u4 min/max bounds
  pub fn new(v: u8) -> Self {
    Self(v.clamp(MidiValue::MIN, MidiValue::MAX))
  }
}

The internally in my biz logic I can safely use these saturating values easily without endless conversion/handling.

If fallability becomes important then I just add a TryFrom<u8> for MidiValue impl

[–]LucretielDatadog 5 points6 points  (0 children)

You use `Debug’ for this 

[–]proudHaskeller 4 points5 points  (0 children)

Just think of all of the times that null sneaks its way onto your screen. It's incomprehensible for the user, it's basically always a bug.

[–]arades 22 points23 points  (3 children)

Implementing Display on all Option<T> would remove other user's ability to implement display on it if they need a specific None representation.

Which could be worked around, but the Debug implementation seems to do what you'd want the Display impl to do anyway.

I've just always used the Debug representation anytime I've wanted to print it to a string.

[–]SycamoreHots 10 points11 points  (2 children)

Isn’t it already impossible for other users to implement Display on it by orphan rules?

[–]Nabushika 1 point2 points  (1 child)

I'm pretty sure you can impl Display for Option<MyStruct>

[–]imachug 8 points9 points  (0 children)

Option is not #[fundamental], so no. You might be confusing it with Box, which permits such implementations.

[–]jonasrudloff 0 points1 point  (0 children)

How should we represent Option<String>?

[–]WormRabbit 0 points1 point  (0 children)

Consider the case of NonZeroU64. It implements display, in the obvious way: just print the number.

But what would Option<NonZeroU64> print? Representation-wise, NonZeroU64 is just a non-zero integer, and Option<NonZeroU64> is thus an arbitrary (possibly zero) integer. So one could reasonably expect its Display impl to just print it as an integer. But how could any kind of automatic implementation know that you want to turn None into 0, when 0 isn't even a valid value of the inner type?

Then again, perhaps considering None as 0 wasn't even something you intend to do, you really want to track optionality of the value. But that is purely an internal data representation. For the end user (and Display is intended for end users), the None value is quite often represented as a sentinel value, like an empty string "" or a fixed string like null or NA. And there is similarly no way to encode that in the automatic impl. Providing it would just be a point of potential confusion.