Why does this compile?
const std = @import("std");
pub fn main() !void {
const foo = std.debug.print("Hello, world\n", .{});
_ = foo;
}
std.debug.print() returns void, but Zig doesn’t complain if I try to assign it to foo.
This came up for me in a project I’m working on, where I mistakenly assumed some API in a third-party lib was returning a value when in fact it wasn’t. I later triggered a compiler error by trying to format the returned value into a string with {s}, but it took me a while to work backward to the original mistake.
Are there cases where assigning void to a variable is useful?
It makes code where the value may or may not exist (determined at comptime) easier.
Instead of dissecting the function type, which may have layers of error unions to go through, its much easier to just check the type of the variable you assign it to.
And you might want to call the function regardless if it does other things.
There isn’t much harm in having an otherwise useless value.
1 Like
I’m curious when and what “discarding”-s items should be put in a test block? For example the std.zig module has such code at the bottom of source code:
test {
_ = Ast;
_ = AstRlAnnotate;
_ = BuiltinFn;
_ = Client;
_ = ErrorBundle;
_ = LibCDirs;
_ = LibCInstallation;
_ = Server;
_ = TokenSmith;
_ = WindowsSdk;
_ = number_literal;
_ = primitives;
_ = string_literal;
_ = system;
_ = target;
_ = c_translation;
_ = llvm;
_ = @import("zig/parser_fuzz.zig");
}
Should I put all imported modules into that for a library? Or what modules should be done like that?
Only the root file will implicitly have its tests run, for other files/modules/namespaces you need to reference the namespace the tests are in. And this includes any struct/union/enum that has tests in that root file, they have to be referenced too.
And to be clear, foo.bar does not reference foo, only foo.bar, whereas _ = foo references foo.
2 Likes