There isn’t a really easy way, you may think you could do meta programming like this:
const std = @import("std");
const windows_c = struct {
const DWORD = u32;
};
const ConsoleInputMode = packed struct(windows_c.DWORD) {
const info = @typeInfo(ConsoleInputMode).@"struct";
const used_fields = info.fields.len - 1;
const needed_padding = @bitSizeOf(info.backing_integer) - used_fields;
const PaddingInt = std.meta.Int(.unsigned, needed_padding);
ENABLE_PROCESSED_INPUT: bool, // 0x0001
ENABLE_LINE_INPUT: bool, // 0x0002
ENABLE_ECHO_INPUT: bool, // 0x0004
ENABLE_WINDOW_INPUT: bool, // 0x0008
ENABLE_MOUSE_INPUT: bool, // 0x0010
ENABLE_INSERT_MODE: bool, // 0x0020
ENABLE_QUICK_EDIT_MODE: bool, // 0x0040
ENABLE_EXTENDED_FLAGS: bool, // 0x0080
ENABLE_AUTO_POSITION: bool, // 0x0100
ENABLE_VIRTUAL_TERMINAL_INPUT: bool, // 0x0200
padding_bits: PaddingInt = undefined,
};
pub fn main() !void {
const original_input_mode: u32 = 0;
var requested_in_mode: ConsoleInputMode = @bitCast(original_input_mode);
requested_in_mode.ENABLE_PROCESSED_INPUT = false;
requested_in_mode.ENABLE_LINE_INPUT = false;
requested_in_mode.ENABLE_ECHO_INPUT = false;
requested_in_mode.ENABLE_MOUSE_INPUT = false;
requested_in_mode.ENABLE_QUICK_EDIT_MODE = false;
requested_in_mode.ENABLE_VIRTUAL_TERMINAL_INPUT = true;
std.debug.print("requested_in_mode: {}\n", .{requested_in_mode});
}
However that won’t work because the calculation has a dependency loop, it depends on the type ConsoleInputMode which then depends on the result of the computation.
paddingbits.zig:15:5: error: dependency loop detected
const PaddingInt = std.meta.Int(.unsigned, needed_padding);
^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
referenced by:
ConsoleInputMode: paddingbits.zig:11:33
main: paddingbits.zig:33:28
Would be interesting if the compiler could get a more fine-grained implementation that would not trigger such a dependency loop, because strictly speaking the calculation only depends on the number of fields, which isn’t influenced by the calculation itself. But because it triggers the dependency loop, there isn’t a simple implementation.
One way (I don’t find very satisfactory) would be to define the fields in a normal struct and use that to create the actual packed struct. The reason why I don’t find it very good, is that I prefer structs that weren’t created through meta programming, because they can contain methods and declarations (and are easier to read).
const std = @import("std");
const windows_c = struct {
const DWORD = u32;
};
const ConsoleInputModeFields = struct {
ENABLE_PROCESSED_INPUT: bool, // 0x0001
ENABLE_LINE_INPUT: bool, // 0x0002
ENABLE_ECHO_INPUT: bool, // 0x0004
ENABLE_WINDOW_INPUT: bool, // 0x0008
ENABLE_MOUSE_INPUT: bool, // 0x0010
ENABLE_INSERT_MODE: bool, // 0x0020
ENABLE_QUICK_EDIT_MODE: bool, // 0x0040
ENABLE_EXTENDED_FLAGS: bool, // 0x0080
ENABLE_AUTO_POSITION: bool, // 0x0100
ENABLE_VIRTUAL_TERMINAL_INPUT: bool, // 0x0200
};
const BackingInt = windows_c.DWORD;
fn PaddingInt() type {
const info = @typeInfo(ConsoleInputModeFields).@"struct";
const used_fields = info.fields.len;
const needed_padding = @bitSizeOf(BackingInt) - used_fields;
return std.meta.Int(.unsigned, needed_padding);
}
const ConsoleInputMode = blk: {
const info = @typeInfo(ConsoleInputModeFields).@"struct";
const len = info.fields.len + 1;
var fields: [len]std.builtin.Type.StructField = undefined;
for (info.fields, 0..) |f, i| {
fields[i] = f;
fields[i].alignment = 0;
}
fields[len - 1] = .{
.name = "padding_bits",
.type = PaddingInt(),
.default_value_ptr = &@as(PaddingInt(), undefined),
.is_comptime = false,
.alignment = 0,
};
break :blk @Type(.{
.@"struct" = .{
.layout = .@"packed",
.fields = &fields,
.decls = &.{},
.backing_integer = BackingInt,
.is_tuple = false,
},
});
};
pub fn main() !void {
const original_input_mode: u32 = 0;
var requested_in_mode: ConsoleInputMode = @bitCast(original_input_mode);
requested_in_mode.ENABLE_PROCESSED_INPUT = false;
requested_in_mode.ENABLE_LINE_INPUT = false;
requested_in_mode.ENABLE_ECHO_INPUT = false;
requested_in_mode.ENABLE_MOUSE_INPUT = false;
requested_in_mode.ENABLE_QUICK_EDIT_MODE = false;
requested_in_mode.ENABLE_VIRTUAL_TERMINAL_INPUT = true;
std.debug.print("requested_in_mode: {}\n", .{requested_in_mode});
}
So basically I don’t think this solution is very good / worth it. I think the best way would be to just go with your original solution and type the correct number there.
If the number is wrong you already get a compile error, so you can’t really pick the wrong number. (at least if you have tried to compile for the target, for cases where the backing type changes for different targets)