Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Remove void{} from the language #15213

Open
mlugg opened this issue Apr 8, 2023 · 10 comments
Open

Remove void{} from the language #15213

mlugg opened this issue Apr 8, 2023 · 10 comments
Labels
proposal This issue suggests modifications. If it also has the "accepted" label then it is planned.
Milestone

Comments

@mlugg
Copy link
Member

mlugg commented Apr 8, 2023

TLDR

  • void{} is unused in all real code
  • void{} is a pointless second way of achieving the same thing as {}
  • void{} is a special case which prevents T{} syntax from being for aggregate initialization only
  • void{} should therefore be removed from the language.

Not Long Enough, Let Me Read

Currently, there are two ways to write a void literal: {}, and void{}.

{} is the canonical form: it's explicitly recommended over void{}, works in every case void{} does, and is used in all Zig code I've ever seen. The form void{} does not exist in the entire compiler and standard library, and I've never seen it used in the wild. There seems to be no practical reason for it to exist.

Despite its lack of use, I'd argue its existence still violates the Zen of Only one obvious way to do things. To someone not explicitly familiar with Zig's conventions, it's not at all clear which is preferable: maybe explicitness is preferred?

The form void{} also leads to an inconsistency regarding initialization syntax. In general, it is a rule that .{} is an aggregate initializer, not a general type initializer (you can't initialize a u32, a *T, etc with it). That's why, in stage2, it was made that .{} is no longer a valid initializer for void values. However, in every other case in the language, it is the case that .{} is precisely equivalent to T{} where T is the type being initialized; therefore, we can also say that T{} for any T is aggregate initialization syntax. Since void is not an aggregate, maintaining the void{} syntax is inconsistent, particularly with the removal of .{} initialization for void (which is, for the record, a decision I agree with).

The void{} form should be eliminated from the language. A zig fmt fixup would be nice, but honestly isn't even really necessary IMO since I've genuinely never seen anyone use this form.

@Vexu Vexu added the proposal This issue suggests modifications. If it also has the "accepted" label then it is planned. label Apr 8, 2023
@Vexu Vexu added this to the 0.12.0 milestone Apr 8, 2023
@rohlem
Copy link
Contributor

rohlem commented Apr 9, 2023

I've been using it (in "real code" no less), but I'll stop now.
If someone really wants to keep using it, they can carry on with const Void = struct{};.

@Jarred-Sumner
Copy link
Contributor

Jarred-Sumner commented Apr 9, 2023

void{} is unused in all real code

The void{} form should be eliminated from the language. A zig fmt fixup would be nice, but honestly isn't even really necessary IMO since I've genuinely never seen anyone use this form.

I can find at least 190 real uses of void{}:

image

That being said, makes sense to remove it imo (and it's easy to find-and-replace)


just to play devil's advocate:

which code snippet is easier to read?

std.sort.sort(
    WatchItemIndex,
    evict_list[0..evict_list_i],
    void{},
    std.sort.desc(WatchItemIndex),
);
std.sort.sort(
    WatchItemIndex,
    evict_list[0..evict_list_i],
    {},
    std.sort.desc(WatchItemIndex),
);

Jarred-Sumner added a commit to oven-sh/bun that referenced this issue Apr 9, 2023
@nektro
Copy link
Contributor

nektro commented Apr 9, 2023

i generally support this proposal but i will add that fwiw void{} is more grep-able than {}

@wooster0
Copy link
Contributor

wooster0 commented Apr 9, 2023

It doesn't really matter how grepable it is if nobody uses it.

@mlugg
Copy link
Member Author

mlugg commented Apr 9, 2023

More to the point, when would you ever need to grep for void constants?

@mlugg
Copy link
Member Author

mlugg commented Apr 10, 2023

@Jarred-Sumner

just to play devil's advocate:

which code snippet is easier to read?

std.sort.sort(
    WatchItemIndex,
    evict_list[0..evict_list_i],
    void{},
    std.sort.desc(WatchItemIndex),
);
std.sort.sort(
    WatchItemIndex,
    evict_list[0..evict_list_i],
    {},
    std.sort.desc(WatchItemIndex),
);

I find the second snippet easier to read (in the sense that I understand it faster); when I was less experienced with Zig, I suspect I would have had no strong preference.

dylan-conway pushed a commit to oven-sh/bun that referenced this issue Apr 10, 2023
@andrewrk andrewrk modified the milestones: 0.13.0, 0.12.0 Jul 9, 2023
@sigod
Copy link

sigod commented Aug 15, 2023

I would argue in favor of removing {} and keeping void{}. Because of:

  • Consistency. void{} is consistent with the syntax for other types, e.g. Foo{}. Why have a different syntax for void?
  • Newcomer friendliness. I remember being confused when seeing {} for the first time. It makes one think that it was supposed to be .{}, but a dot was lost. It's basically a magic syntax, you have to know that the result is void.
  • grep-ability. As already mentioned.

which code snippet is easier to read?

The first one.

@mlugg
Copy link
Member Author

mlugg commented Aug 15, 2023

  • Consistency. void{} is consistent with the syntax for other types, e.g. Foo{}. Why have a different syntax for void?

As addressed in the original issue post, this syntax is inconsistent with the rest of the language, since T{} is otherwise an aggregate initializer.

  • Newcomer friendliness. I remember being confused when seeing {} for the first time. It makes one think that it was supposed to be .{}, but a dot was lost. It's basically a magic syntax, you have to know that the result is void.

How is {} any more of a "magic syntax" than .{}? It's just a syntax, which you need to learn - it's pretty simple and is easy to remember once you know what it is. Having to learn some basic bits of syntax is kind of just how programming languages work.

  • grep-ability. As already mentioned.

As I previously asked: why does this actually matter? When do you need to grep your codebase for void values?

@rohlem
Copy link
Contributor

rohlem commented Aug 15, 2023

It's basically a magic syntax

@sigod The syntax arises naturally from being an empty unlabeled block {}.
In status-quo you can already write a block of instructions as any expression, though it has to be labeled if you intend to return a non-void value:

const std = @import("std");
const a: u32 = 8;
const b: u32 = b: { // constructed using a block instead of just a literal
    // arbitrary statements
    std.debug.assert(10 > 5);
    break :b 8;
};
const c: void = c: { // now we provide `void` instead of `u8` as value
    std.debug.assert(10 > 5);
    break :c void{};
};
const d: void = { // providing no value implicitly provides a void value
    std.debug.assert(10 > 5);
    // comparable to how `void` functions aren't required to have a `return` statement
};
const e: void = {}; // same as above, but empty (has no statements)
const f = {}; //same as e, but type `void` is deduced

And as I've pointed out before, if you prefer the syntax void{} and want to enforce it in your own code:

pub const Void = struct{}; // use this instead of `void` and now you _have to use `Void{}` instead of `{}` everywhere!

@InspectorBoat
Copy link

InspectorBoat commented Oct 23, 2023

How is {} any more of a "magic syntax" than .{}? It's just a syntax, which you need to learn - it's pretty simple and is easy to remember once you know what it is. Having to learn some basic bits of syntax is kind of just how programming languages work.

Using {} for void isn't a result of syntax, but rather semantics. void just happens to be the value of an empty block. void{}, on the other hand, is syntax. It's also more explicit, and also more obvious to beginners.

Since void is not an aggregate, maintaining the void{} syntax is inconsistent, particularly with the removal of .{} initialization for void (which is, for the record, a decision I agree with).

As a zero sized type, void is isomorphic to all other zero sized types, many of which are composite types: struct{}, enum {Foo}, [0].{}. Rohlem even pointed out pub const Void = struct{}; as a replacement for void. So one could say void is in some sense similar to an composite type with no components.

Of course, this analogy isn't quite perfect: u0, for example is also a zero sized type, and isn't a composite type. However, I think the analogy makes enough sense for void{} to make sense.

ZeusMystery added a commit to ZeusMystery/bun that referenced this issue Jan 5, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
proposal This issue suggests modifications. If it also has the "accepted" label then it is planned.
Projects
None yet
Development

No branches or pull requests

9 participants