Why no error on pointless discard of `void`?

This code does not produce a compile error:

fn foo() void {
    return;
}

// compiler should say pointless discard for this function
fn hasPointlessDiscard() void {
    _ = foo();
}

test "this pointless discard should raise compile error" {
    hasPointlessDiscard();
}

Why no “pointless discard” compile error for void types?

I guess one could argue that a library user that wishes to discard a function return value would not be pleased to have to un-discard a void when the library API changes?

Because void is type, and a value for void type is void{}.

const std = @import("std");
	
pub fn main() !void {
    const v: void = void{};
    std.debug.print("Void value: {}\n", .{v});
}

Result:

$ zig build run
Void value: void

See also:
https://ziglang.org/documentation/master/#toc-Primitive-Types

1 Like

Pointless discard errors are for when you discard a value, and then use it later. In this case there is no pointless discard.

Now you may ask, why do we even allow assigning values with type void at all? Remember void is different in zig than in C. In Zig void is a zero size type, where as in C it can be used like anyopaque (or at least void *). Where void comes in handy is in comptime code, where under some conditions you don’t want a value to exist. You’ll see this in the standard library a lot. A struct will be defined, but the type of a field on it may be void, depending on the OS being compiled for.

I think the question may be, “should we error if someone attempts to assign void values to a variable”? Certainly at runtime the semantics make little sense. But it could make sense at comptime, and the type checks should catch if you try to use void as anything else.

2 Likes

I think the answer is simply that it’s allowed so that generic code can be written more easily. Other cases of pointless discard are also followed up by actual use of the discarded variable, while in this case you can’t really use a void value, so no harm is done by allowing it.

Or maybe it’s just an oversight and a check will be added eventually :^)

1 Like

It makes sense at runtime also, tagged union variants can have a void payload. Just like discarding void returns, we have syntax sugar for void union variants, just use the enum literal. But const my_union: TaggedType = .{ .void_kind = {}}; is the sugar-free version, and it works.

That’s the main thing here: void is a type, it can have instances, one can return it, assign it, compare it and so on. There’s syntax sugar for using it, but it’s better when syntax sugar has unsugared forms: since void can be assigned, it can also be discarded, the _ = is optional but it wouldn’t make sense to me if it were an error.

However, if the formatter automatically removed the discard, the way it turns an identifier .@"plain_id" into just plain_id, I wouldn’t mind that at all.

1 Like