Zig: Proposal: make initializing a zeroed struct naturally safe

Created on 30 Aug 2020  Â·  8Comments  Â·  Source: ziglang/zig

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.

proposal

Most helpful comment

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)

All 8 comments

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, or std.mem.zeroInit would 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.

Was this page helpful?
0 / 5 - 0 ratings