What are .fieldname

I am reading through the build.zig in tigerbeetle here

and I see this

    // Build options passed with `-D` flags.
    const build_options = .{
        .target = b.option([]const u8, "target", "The CPU architecture and OS to build for"),
        .config = b.option(config.ConfigBase, "config", "Base configuration.") orelse .default,
        .config_aof_record = b.option(
            bool,
            "config-aof-record",
            "Enable AOF Recording.",
        ) orelse false,
        .config_aof_recovery = b.option(
            bool,
            "config-aof-recovery",
            "Enable AOF Recovery mode.",
        ) orelse false,
        .config_log_level = b.option(std.log.Level, "config-log-level", "Log level.") orelse .info,
        .config_release = b.option([]const u8, "config-release", "Release triple."),
        .config_release_client_min = b.option(
            []const u8,
            "config-release-client-min",
            "Minimum client release triple.",
        ),
        // We run extra checks in "CI-mode" build.
        .ci = b.graph.env_map.get("CI") != null,
        .emit_llvm_ir = b.option(bool, "emit-llvm-ir", "Emit LLVM IR (.ll file)") orelse false,
        // The "tigerbeetle version" command includes the build-time commit hash.
        .git_commit = b.option(
            []const u8,
            "git-commit",
            "The git commit revision of the source code.",
        ) orelse std.mem.trimRight(u8, b.run(&.{ "git", "rev-parse", "--verify", "HEAD" }), "\n"),
        .hash_log_mode = b.option(
            config.HashLogMode,
            "hash-log-mode",
            "Log hashes (used for debugging non-deterministic executions).",
        ) orelse .none,
        .vopr_state_machine = b.option(
            VoprStateMachine,
            "vopr-state-machine",
            "State machine.",
        ) orelse .accounting,
        .vopr_log = b.option(
            VoprLog,
            "vopr-log",
            "Log only state transitions (short) or everything (full).",
        ) orelse .short,
        .tracer_backend = b.option(
            config.TracerBackend,
            "tracer-backend",
            "Which backend to use for tracing.",
        ) orelse .none,
    };

And I am confused about the syntax. Specifically what are the .default, .none, .info, .accounting etc?

Also I understand one can use .{} as a shortcut to not specifying the struct name. So for example

const person: Person = .{.name = "joe"}

instead of

const person: Person = Person{.name = "joe"}

But in the case of the code above, it is just:

const build_options = .{...}

There is no where it is stated what the type of build_options should be. So how does this work?

  1. Those are enum values, for instance, .none is the value of config.HashLogMode enum, which is specified as the first argument to b.option.

  2. .{} is the syntax of tuples, anonymous struct literals or anonymous union literals. Btw, you can also write:

const person = Person{.name = "joe"};

Still scratching my head because I am wondering how we know the type we are dealing with is config.HashLogMode given we only defining the value via

const build_options = .{...}

It doesn’t need to be a named type, that’s why it’s called anonymous.

Never mind. I see in the definition the type is specified. ie

        .hash_log_mode = b.option(
            config.HashLogMode,
            "hash-log-mode",
            "Log hashes (used for debugging non-deterministic executions).",
        ) orelse .none,

there is the type being specified as config.HashLogMode

Yes, that’s what I meant by:

1 Like

Yes. But as always I was impatient and did not completly parse what you wrote. Thanks!

2 Likes

They’re enum literals. Like other comptime types, they can be coerced into any enum, provided that the enum contains an item by that name. Take .default, for example. If you cast it into an enum { default, cow, bear }, then it becomes the first item of that particular enum. If you cast it into an enum { dog, cat, oj_simpson, default }, it becomes the fourth item.

The behavior is analogous to how comptime_int works. If you have the statement const zero = 0;, zero is a comptime_int. It can be coerced into any integer and float type. u8, u42, f128, whatever.

Anonymous structs work in the same way. .{ .name = "Sam" } can represent an infinite number of different struct types. Here’re three that satisfy the constraints:

const A = struct {
    name: []const u8,
};

const B = struct {
    number: f64 = 0.1234,
    name: []const u8,
};

const C = struct {
    pink: usize = 0,
    name: []const u8,
    blue: ?A = null,
};

Comptime types are like the cat before you open the box. They have no form until you coerce them into runtime types.

3 Likes