Impl Display for ()

So I just got bitten by the fact that () doesn't implement std::fmt::Display.

My question is, why isn't it implemented? It seems to me that this impl would be as simple as impl blocks come, yet it doesn't exist.
Which leads me to believe there is more going on here.
What is that something more?

1 Like

So what does it print as then? An empty string? The literal Rust source-code representation of "()"? (In my experience literal Rust source-code representations are more of a Debug kind of thing.)

Also, I think there’d be potential for confusion, that every function foo(args…) that “doesn’t return anything” would now start to support foo(args…).to_string(). But that’s probably best discussed by looking for more concrete examples of common API that “sounds like it might return something” by its name.

7 Likes

The latter. That's the literal used for it, so that's what I would expect.

Ah so that's why.

At the same time, the current behavior is surprising, to say the least. I fully expected a Display impl to be present for (), otherwise I'd have just used the workaround I now need (i.e. a ZST with a Display impl) from the start.

A bit of context might be useful.
I have a generic type that has a manual Display impl, and it requires that the type variables implement Display. Currently I'm using it with those type variables instantiated to (), and that's where this bit me.
So it's not that I intrinsically want to print (); Instead it's that it's a dependency for what I really need.

1 Like

To clarify, there’s no technical problems with writing such an impl. In fact, many ZSTs in std have Display implementations: all those error types without additional data, like - idk - LayoutError for example (though those generally contain private fields or #[non_exhaustive] marker to allow future addition of more fields.


Yes, context could be super interesting here. If you never want to print the () (i.e. never generate the string "()" from it), why does your use-case end up requiring the Display implementation though? Or are there additional run-time checks for whether something gets printed at all that end up never invoking the Display for the contained ZST, after all? (If never printing it is the goal, maybe a Display implementation that just panics is most useful for catching all possible bugs? :thinking:)

(In other words, feel free to provide even more context :smiling_face: :crab: :tropical_fish:)

2 Likes

I never really expected such an impl to be unwritable. Even if there had been some issue, it could always have been implemented as

impl std::fmt::Display for () {
  fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
    write!(f, "()")
 } 
} 

i.e. the impl doesn't even need anything from self.
That's why I suspected something else had to be going on that didn't just hinge on whether or not an impl was possible at all.

It's not so much that I don't want to print it, it's that it's not something I want to do directly; rather it's an indirect requirement, and the Display impl for my generic type then uses that to give a nice output.

So it's not all too dissimilar to printing a struct, where all fields need to implement Display. It's just that in this case it's a field-of-a-field, and that type happens to be a type variable, and thus it needs to implement Display too.

Btw, I was able to find an closed issue


And an IRLO discussion


And a reddit thread, not discussing the lack of that impl, but at least containing a user where Display for () would have turned a compile-time error into run-time misbehavior (because they printed the “return value” of i += 1).


And a post on … howtosolutions.net (never seen that before) edit: Oh, that’s just someone’s personal blog website! where a user described a situation where, too, they tried to print something that didn’t have a value, this time because of wrong additional semicolon in a closure.

3 Likes

Well I have mixed feelings about those discussions.

On the one hand, I can't help but feel that () is somewhat neutered in its usefulness because of the ultimate decision not to support a Display impl.

On the other hand, I'm glad to see that there is at least decent reasoning behind the status quo, as opposed to e.g. an oversight.

That's probably the reason I can agree with most so far; that might actually be a footgun, albeit a really tiny .022 caliber (that's not a typo) because it's really easy to spot if one bothers to check.

Note that the typical case of "printing all fields of a struct" involves recursing using the Debug trait, not Display, and that Debug is implemented for (), using Rust syntax in the way you expect (though that is not guaranteed).

If you want something that prints nested structs in a way that isn't Debug, you'll need to define a new trait for “format the way I want”. Or you could use my library manyfmt, which lets you conveniently define "another formatting trait" (actually a type parameter to a generic trait) while still semi-conveniently integrating with write!/format!.

3 Likes

I wouldn't call this particular piece of code typical. Besides, whether or not one traverses the struct in Display fully depends on what the struct data represents, as well as on how one wants to represent it to the user.
Trust me, this isn't an abuse of Display :wink:

Trust me, this isn't an abuse of Display :wink:

I don't mean that using Display to print your desired format is an abuse. I meant that one shouldn't take expectations from Debug (that recursive printing of a struct with fields can work by default) and apply them to Display.

Besides, whether or not one traverses the struct in Display fully depends on what the struct data represents, as well as on how one wants to represent it to the user.

Yes. So. Let's talk about that. Let me frame it like this:

You want to print a certain data structure in a certain format, using the trait system. In order for the compiler to know the format you want, it must be specified to it via either the data type, or the trait.

  • Specifying it via the data type looks like impl Display for MyAppStruct or impl Display for MyWrapper — defining types that know how they are to be displayed.
  • Specifying it via the trait looks like impl LowerHex for MyAppStruct — picking a trait that means the format you want.

When you Display a (), there's no room to specify the format because neither the type nor the trait are yours, and as already discussed, () hasn't got a canonical display format (I am not going to get into that argument again). So, by the logic of Rust's trait system, you have to add something to convey your desired format. Otherwise the compiler can't know what you want.

If you want, you can do this and still use Display everywhere, using a wrapper type:

use std::fmt;

/// Generic “display this *my* way” wrapper.
struct Dw<'a, T>(&'a T);

/// This is your generic struct that might contain an `()`
struct MyAppStruct<T> {
    left: T,
    right: T,
}

/// This is a mostly ordinary `Display` implementation
impl<T> fmt::Display for MyAppStruct<T>
where
    for<'a> Dw<'a, T>: fmt::Display,
{
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        let Self { left, right } = self;
        write!(
            f,
            "On the left we have {}, and on the right we have {}.",
            Dw(left),
            Dw(right),
        )
    }
}

// Now we define how we want various primitives to be formatted for our application.

impl fmt::Display for Dw<'_, ()> {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        f.write_str("nothing")
    }
}
impl fmt::Display for Dw<'_, bool> {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        f.write_str(if *self.0 { "truth" } else { "falsity" })
    }
}

This is a simpler, less generic version of the technique manyfmt uses.

1 Like

Wouldn't the same argument apply to bool? It implements Display that prints "true" or "false".

The (a, b, c) notation is standard in math for tuples, so it seems reasonable to me to use that in Display.

1 Like

Nah I just took the less complex way out: I replaced () with a ZST Nada, with which I can do whatever I need to.
Adding additional machinery would just obscure the intent more than necessary.

This is a good point. Unit / () is essentially a nullary tuple.

So how does that work? ("a", "b", "c"), ("a, b", "c"), ("a", "b, c"), and "(a, b, c)" all print the same thing? I mean that works... but I’m not sure how far the use proper cases for this go. (Realistically, the math analogy only sufficiently well for tuples containing numbers, nor arbitrary Display types; also not really for unary tuples, I’ve never seen (a,) outside of Rust. And empty tuples are comparatively rare in math, too, anyways...)

2 Likes

Sure I'm not arguing for extending the functionality anymore, as far as I'm concerned my problem is solved.
But conceptually I think it's exactly right that unit is a nullary tuple. It's how I've always viewed it ever since I started using Rust, and probably before due to my Lisp usage in years past.

100% unit is a 0-ary tuple, no doubt. I was just saying that empty tuples are a bit more rare in mathematics. I've only seen them regularly in the context of theoretical computer science, where for some alphabet A, the set of words over A, written A*, is a set of all tuples of all lengths with elements from the alphabet A. In that context however, it's common to just juxtapose letters to form a word and to write the empty tuple as an epsilon: ε.

To name a different example, if you consider tuples of numbers, e. g. to form the vector space ℝn whose elements are typically also tuples, technically, (though then vertical vector notation is super common), you usually rule out n=0 because it's a trivial case (and not even a vector space), and even for n=1 you usually “identify” single element tuples with their contents, and don't distinghst ℝ1 from ℝ much.

In the context of set theory, I've had one professor use angled brackets like ⟨a, b, c⟩ for tuples, as it helps differentiate a single-element one ⟨a⟩ from parenthesized expression. Then the empty one could have been written ⟨⟩, though possibly also ∅ because formally it turns out it would have just been the empty set. In that context, we most often used indexed families notation anyways... like ⟨ajj∈J, and index sets were often infinite (sometimes uncountably so using ordinal numbers, sometimes even proper classes of ordinal numbers), which isn't making for you most common/standard mathematical type of "tuple" anymore either, I suppose.

Nitpicking, but ℝ0 is totally a vector space. No reason to skip it, a totally valid (and useful!) space.

1 Like

Oh, my bad, I should really check such claims with the definition. I mixed this up with rings and fields where {0} usually isn't considered sufficient, IIRC.

On that note though, you'd write the single member in ℝ0 as 0 even when its formally an empty tuple, because it's also the zero vector of that space.

You'll get no disagreement from me.
It's just that even though those concepts are in a sense equivalent, that doesn't mean they're used the same way.
An analogy could be that even though The Howard-Curry correspondence exists, and is well-known, usually¹ we don't use types for the purposes of formal logic, nor do we generally use Prolog (directly, syntactically) to type our programs.

My point with that is that I would just use it for its intended purpose, and the fact that it's a mathematical obscurity is both true and kind of sort of irrelevant, at least to me. Just because it would be useful to me.
That's also why I would expect Unit to Displayed as (), had such an impl existed: I wouldn't consider the mathematical side, just the language usage side.

¹ I'm sure one could find exceptions if one tried hard enough. Perhaps provers like Lean or Coq get fairly close in terms of blurring the line.