Nesting struct types or not?

Consider the following two examples:

  1. “nested”
pub const Car = struct {
    wheels: [4]Wheel,

    pub const Wheel = struct {
        diameter_inches: f64,
        lug_nut_count: u8,
    };
};
  1. “flat”
pub const Wheel = struct {
    diameter_inches: f64,
    lug_nut_count: u8,
};

pub const Car = struct {
    wheels: [4]Wheel,
};

Coming from python, my brain has been stuck in interpreter mode for so long (“the interpreter hasn’t seen wheel yet so I better put it up top!”) that I used “flat” for a long time, but as I grow zig scales I am reaching for “nested” more, especially if wheel is only applicable to cars, like the following example:

pub const Datagram = struct {
    header: Header,
    data: []const u8,
    wkc: u16,
    pub const Header = packed struct(u80) {
        command: Command,
        idx: u8 = 0,
        address: u32,
        length: u11,
        reserved: u3 = 0,
        circulating: bool,
        next: bool,
        irq: u16,
    };
};

And now when I need to instantiate a datagram header I get a much more readable:

Datagram.Header{...}
6 Likes

I also prefer nesting. The namespacing you get makes things much more readable in my opinion. You can also use the same name in more than one place (nested under different parents, of course).

4 Likes

I am generally for nesting too. But it may depend on a subject field.
For example if I had a Bus (along with a Car) I’d probably made Wheel a separate entity.

You can place sub-struct before the fields:

pub const Car = struct {
    pub const Wheel = struct {
        diameter_inches: f64,
        lug_nut_count: u8,
    };
    wheels: [4]Wheel,
};

Oh, I have real 3-level example.
StageMachine has State inside which in turn has Reflex inside.

2 Likes

Worth noting that any struct type is nested in another unless it’s explicitly the file-level @This() struct.

Whatever reads best is as good a guide as any to how to organize type definitions. But that could also be:

const datagram = @import("datagram.zig");

const Datagram = datagram.Datagram;
const Header = datagram.Header;
// rather than datagram.Datagram.Header

This is the sort of layout I tend to use, but recently I’ve come to see that as a habit left over from other languages. It isn’t natural for me, yet, to ask “ok is this file primarily defining one struct type” and if so, to put the fields directly into the file container and so on.

There’s one defensible reason I’ve avoided this: it’s easier to refactor a struct definition into a type-returning function if the struct isn’t file-level, and I’ve made that change more than once at this point. But it isn’t that much easier.

Of course, if you nest other types inside the type which has them as a field, then your code has access to private state used in the outer type. If that’s the case then nesting is in fact necessary.

I don’t see an obvious best practice on this question other than whatever reads best, though.

3 Likes