Why . before field name in struct?

This works nicely:

const expect = @import("std").testing.expect;

const Stuff = struct {
    x: i32,
    y: i32,
};

test "01" {
	const thing = Stuff {.x = 10, .y = 20};	
	try expect(thing.x == 10);
}

Why do I have to use the . before x and y in the first line of the test?

Best regards,
Catalin

4 Likes

Such field initialization syntax is just easier to parse. Once the tokenizer sees a . after a block start { it can immediately tell that’s a field initialization, which means less ambiguity and more performance. The choice of . for field initialization is also consistent since you already write a . every time you access a field.

8 Likes

Thanks!

It seems strange to me, as this . is not necessary for field initialization in other languages, but I can understand that it is a design choice in order to speed up the parsing of the code. I was trying to understand if my code is doing something special because of the dot, besides just initializing the field.

I see that patterns/variations of .{} are used a lot in Zig. In this line of code:

std.debug.print("\nStuff = {}\n", .{@TypeOf(Stuff)});

Why do I need to have @TypeOf(Stuff) in a block and a dot preceeding the block?

Best regards,
Catalin

2 Likes

Yeah, so .{} is the anonymous struct syntax and here it’s used to be able to pass to the std.debug.print a variable number of arguments. This design decision eliminates the complexity of having variadic functions like in C.

And again, such anonymous struct syntax is really easy to parse since by the time you parse .{ you already know what it is.

2 Likes

It is also tremendously easier to grep. If I want to learn where foo field is set, I can search for .foo = and that’ll give me all occurrences except for meta-programming via @field.

Now, of course, with good IDE support you can just the IDE to look for usages of foo which are only writes or inits. But good IDE support is still pretty thin these days, IIRC, LSP, for example, doesn’t allow expressing such a query at all.

6 Likes

True, although be careful cause that also won’t give you the occurrences of default field values being set.

2 Likes

I believe that this dot before field name syntax is also used in C, so this makes transitioning from C to Zig a bit easier too.

That’s true, an example from some my code:

static struct modbus_cmd_descr xdcmf_readout_sequence[] = {
        [0] = {.func = 0x04, .sreg = 0x00E8, .nreg = 0x0002, .copy = true},
        [1] = {.func = 0x04, .sreg = 0x00F6, .nreg = 0x0008, .copy = true},
        [2] = {.func = 0x04, .sreg = 0x0100, .nreg = 0x000A, .copy = true},
        [3] = {.func = 0x04, .sreg = 0x0116, .nreg = 0x0008, .copy = true},
        [4] = {.func = 0x04, .sreg = 0x0124, .nreg = 0x0002, .copy = true},
};

Also note a comma (,) after the last element the array.
Somewhere I saw in Zig it is also recommended to use comma after something that is currently last.

1 Like

Maybe you’re referring to this item in the style guide:

  • If a list of things is longer than 2, put each item on its own line and exercise the ability to put an extra comma at the end.

So for example in a function call, if it takes less than 3 args you write it like:

func(arg1, arg2);

But if it takes 3 or more, you write it like:

func(
    arg1,
    arg2,
    arg3,
    arg4,
);

Zig fmt will automatically format it as per the style guide if you put the comma after the last item.

1 Like

Yes, this is it.

Just a quotation from here:

If you’re like me and not a C++ programmer, the syntax to initalize fields, .$field = $value, might be a little odd, but you’ll get used to it in no time.

I take dots in fields’ initialisers exactly like this - it is consistent with dots when accessing fields.

1 Like

I think there are cases when putting more than 2 (btw, why two, why not three or five?..)
things on one line would be more readable than putting them on separate lines.
This is something like my C example above, but in Zig, and I kept that rule:

const std = @import("std");

const ModbusCmndDesc = struct {
    func: u8,
    sreg: u16,
    nreg: u16,
};

pub fn main() void {

    const someCmdSequence: [3]ModbusCmndDesc = .{
        .{
            .func = 0x04,
            .sreg = 0x00AA,
            .nreg = 1,
         },
        .{
            .func = 0x04,
            .sreg = 0x00BB,
            .nreg = 1,
           },
        .{
            .func = 0x04,
            .sreg = 0x00CC,
            .nreg = 1,
        },
    };

    for (someCmdSequence) |c| {
        std.debug.print("{}\n", .{c});
    }
}

Now note the output:

$ ./aa 
aa.ModbusCmndDesc{ .func = 4, .sreg = 170, .nreg = 1 }
aa.ModbusCmndDesc{ .func = 4, .sreg = 187, .nreg = 1 }
aa.ModbusCmndDesc{ .func = 4, .sreg = 204, .nreg = 1 }

Zig itself (well, a function from standard library) ouputs each struct on a single line.
So why can’t I do this in source code? :slight_smile:

For me

   const someCmdSequence: [3]ModbusCmndDesc = .{
        .{.func = 0x04, .sreg = 0x00AA, .nreg = 1},
        .{.func = 0x04, .sreg = 0x00BB, .nreg = 1},
        .{.func = 0x04, .sreg = 0x00CC, .nreg = 1},
    };

is more readable, just because it naturally has a form of evenly aligned 2-dim table
(provided that names of fields are of the same length, of course). When checking some particular field, it is more convenient to have all instances of that field to be in one column,
due to a way how our vision works.

1 Like

I see your point and I agree with you that it’s more readable.

I encourage you to open a new thread about this topic if you want to continue the discussion about style guides.

Zig fmt supports this formatting too. You’d need to drop the trailing commas from within the inner struct values and it should be laid out like you want it to.

2 Likes

Oh, thanks for this! I did not know .
So, it seems that formatting/style rules are:

  • with trailing comma put every item on it’s own line
  • without trailing comma put everything on single line if you want/like it.
1 Like

Oops, sorry, that was a bit of off-topic, of course.
I do not think it is worth special thread.

1 Like