When working with C, this idiom is common:
struct io_uring_params p;
memset(&p, 0, sizeof(p));
p.flags = flags;
Zig encourages you to initialize all fields of a struct explicitly:
var p: io_uring_params = {
.sq_entries = 0,
.cq_entries = 0,
.flags = 0,
.sq_thread_cpu = 0,
.sq_thread_idle = 0,
.features = 0,
.wq_fd = 0,
.resv = [_]u32{0} ** 3,
.sq_off = undefined,
.cq_off = undefined,
};
p.flags = flags;
However, this is becoming verbose, and there is a footgun here, since sq_off and cq_off are structs and also need to be zeroed, something not made explicit by undefined.
One might be tempted to do:
var p: io_uring_params = undefined;
@memset(@ptrCast([*]u8, &p), 0, @sizeOf(@TypeOf(p)));
p.flags = flags;
Which is probably safe? ...but feels wrong for Zig.
Proposal: make initializing a zeroed struct naturally safe:
var p: io_uring_params = {};
This could be allowed only at initialization, to avoid the case where someone accidentally zeroes a single-field struct after initialization by leaving out a field.
std.mem.zeroes and std.mem.zeroInit are currently standard library functions that can help you with this btw.
(zeroes returns a zero initialized instance of the passed type while zeroInit will zero initialize but keep default values of struct fields)
Thanks @alexnask, I guess default values complicate the semantics of doing var p: io_uring_params = {} since it's unclear if the struct would be 100% zeroed or zeroed with defaults. I guess for C interop there would be no defaults, and zeroed with defaults would then make sense for everything else.
I saw those functions in the std lib, but there are so many choices and I thought it would be good to make this idiom simpler and safer without having to choose which of @memset, std.mem.zeroes, or std.mem.zeroInit to use, and without importing the std lib.
If a struct contains a non-optional pointer, initializing it to all zero is not safe.
Thanks @hiroakitakada, I suppose then for that specific use case that none of @memset, std.mem.zeroes, or std.mem.zeroInit would be safe either?
If it's explicitly a C interface that's recommended to zero init, using std.mem.zeroes or std.mem.zeroInit would be considered "safe" — there's a special escape for extern structs: https://github.com/ziglang/zig/blob/master/lib/std/mem.zig#L195. For pure Zig code, this is probably not true and these functions will most likely lead to a compile error.
const S = extern struct {
a: u32 = 0,
b: u64 = 0,
c: ?*u32 = null
};
var s: S = .{};
std.log.info("{}", .{s});
// S{ .a = 0, .b = 0, .c = null }
Thanks @hiroakitakada, I suppose then for that specific use case that none of
@memset,std.mem.zeroes, orstd.mem.zeroInitwould be safe either?
@memset is not safe, as the document says "This function is a low level intrinsic with no safety mechanisms." std.mem.zeroes and std.mem.zeroInit are safe because the compiler detects unsafe usage, as @fengb wrote.
This is fully addressed and solved by std.mem.zeroInit.
Most helpful comment
std.mem.zeroesandstd.mem.zeroInitare currently standard library functions that can help you with this btw.(zeroes returns a zero initialized instance of the passed type while zeroInit will zero initialize but keep default values of struct fields)