Working around "packed structs cannot contain arrays"

I’ve got a packed struct that really should be an array. What are my options for this at the moment?

pub const SMRegister = packed struct(u2048) {
    SM0: SyncManagerAttributes,
    SM1: SyncManagerAttributes,
    SM2: SyncManagerAttributes,
    SM3: SyncManagerAttributes,
    SM4: SyncManagerAttributes,
    SM5: SyncManagerAttributes,
    SM6: SyncManagerAttributes,
    SM7: SyncManagerAttributes,
    SM8: SyncManagerAttributes,
    SM9: SyncManagerAttributes,
    SM10: SyncManagerAttributes,
    SM11: SyncManagerAttributes,
    SM12: SyncManagerAttributes,
    SM13: SyncManagerAttributes,
    SM14: SyncManagerAttributes,
    SM15: SyncManagerAttributes,
    SM16: SyncManagerAttributes,
    SM17: SyncManagerAttributes,
    SM18: SyncManagerAttributes,
    SM19: SyncManagerAttributes,
    SM20: SyncManagerAttributes,
    SM21: SyncManagerAttributes,
    SM22: SyncManagerAttributes,
    SM23: SyncManagerAttributes,
    SM24: SyncManagerAttributes,
    SM25: SyncManagerAttributes,
    SM26: SyncManagerAttributes,
    SM27: SyncManagerAttributes,
    SM28: SyncManagerAttributes,
    SM29: SyncManagerAttributes,
    SM30: SyncManagerAttributes,
    SM31: SyncManagerAttributes,
};

pub const SyncManagerAttributes = packed struct(u64) {
    physical_start_address: u16,
    length: u16,
    control: SyncManagerControlRegister,
    status: SyncManagerStatusRegister,
    activate: SyncManagerActivateRegister,
    channel_enable_PDI: bool,
    repeat_ack: bool,
    reserved: u6 = 0,
};

// please don't make fun of me for this, packed structs cannot currently contain arrays.
pub fn escSMsFromSIISMs(sii_sms: []const SyncM) esc.SMRegister {
    var res = std.mem.zeroes(esc.SMRegister);

    if (sii_sms.len > 0) {
        res.SM0 = escSMFromSIISM(sii_sms[0]);
    }
    if (sii_sms.len > 1) {
        res.SM1 = escSMFromSIISM(sii_sms[1]);
    }
    if (sii_sms.len > 2) {
        res.SM2 = escSMFromSIISM(sii_sms[2]);
    }
    if (sii_sms.len > 3) {
        res.SM3 = escSMFromSIISM(sii_sms[3]);
    }
    if (sii_sms.len > 4) {
        res.SM4 = escSMFromSIISM(sii_sms[4]);
    }
    if (sii_sms.len > 5) {
        res.SM5 = escSMFromSIISM(sii_sms[5]);
    }
    if (sii_sms.len > 6) {
        res.SM6 = escSMFromSIISM(sii_sms[6]);
    }
    if (sii_sms.len > 7) {
        res.SM7 = escSMFromSIISM(sii_sms[7]);
    }
    if (sii_sms.len > 8) {
        res.SM8 = escSMFromSIISM(sii_sms[8]);
    }
    if (sii_sms.len > 9) {
        res.SM9 = escSMFromSIISM(sii_sms[9]);
    }
    if (sii_sms.len > 10) {
        res.SM10 = escSMFromSIISM(sii_sms[10]);
    }
    if (sii_sms.len > 11) {
        res.SM11 = escSMFromSIISM(sii_sms[11]);
    }
    if (sii_sms.len > 12) {
        res.SM12 = escSMFromSIISM(sii_sms[12]);
    }
    if (sii_sms.len > 13) {
        res.SM13 = escSMFromSIISM(sii_sms[13]);
    }
    if (sii_sms.len > 14) {
        res.SM14 = escSMFromSIISM(sii_sms[14]);
    }
    if (sii_sms.len > 15) {
        res.SM15 = escSMFromSIISM(sii_sms[15]);
    }
    if (sii_sms.len > 16) {
        res.SM16 = escSMFromSIISM(sii_sms[16]);
    }
    if (sii_sms.len > 17) {
        res.SM17 = escSMFromSIISM(sii_sms[17]);
    }
    if (sii_sms.len > 18) {
        res.SM18 = escSMFromSIISM(sii_sms[18]);
    }
    if (sii_sms.len > 19) {
        res.SM19 = escSMFromSIISM(sii_sms[19]);
    }
    if (sii_sms.len > 20) {
        res.SM20 = escSMFromSIISM(sii_sms[20]);
    }
    if (sii_sms.len > 21) {
        res.SM21 = escSMFromSIISM(sii_sms[21]);
    }
    if (sii_sms.len > 22) {
        res.SM22 = escSMFromSIISM(sii_sms[22]);
    }
    if (sii_sms.len > 23) {
        res.SM23 = escSMFromSIISM(sii_sms[23]);
    }
    if (sii_sms.len > 24) {
        res.SM24 = escSMFromSIISM(sii_sms[24]);
    }
    if (sii_sms.len > 25) {
        res.SM25 = escSMFromSIISM(sii_sms[25]);
    }
    if (sii_sms.len > 26) {
        res.SM26 = escSMFromSIISM(sii_sms[26]);
    }
    if (sii_sms.len > 27) {
        res.SM27 = escSMFromSIISM(sii_sms[27]);
    }
    if (sii_sms.len > 28) {
        res.SM28 = escSMFromSIISM(sii_sms[28]);
    }
    if (sii_sms.len > 29) {
        res.SM29 = escSMFromSIISM(sii_sms[29]);
    }
    if (sii_sms.len > 30) {
        res.SM30 = escSMFromSIISM(sii_sms[30]);
    }
    if (sii_sms.len > 31) {
        res.SM31 = escSMFromSIISM(sii_sms[31]);
    }

    return res;
}

Basically I need to construct the packed struct fields from an array.

This…works I guess but its awful.

extern struct is not an option for, is it?

2 Likes

Yeah, I agree with @dee0xeed that extern structs are the solution here.
Also why even use a struct at this point and not just directly use an array?

1 Like

I am using the little endian memory layout of packed structs as a deserialization mechanism for a bit-packed binary protoocol transfered over ethernet.

/// Convert little endian packed bytes from EtherCAT to packed struct in host representation.
pub fn packFromECat(comptime T: type, ecat_bytes: [@divExact(@bitSizeOf(T), 8)]u8) T {
    comptime assert(isECatPackable(T));
    switch (native_endian) {
        .little => {
            return @bitCast(ecat_bytes);
        },
        .big => {
            var bytes_copy = ecat_bytes;
            std.mem.reverse(u8, &bytes_copy);
            return @bitCast(bytes_copy);
        },
    }
    unreachable;
}

I could possible write a more complex generic function to do this that would be compatible with regular structs, arrays, enums, packed structs, everything. But that would require a bit more learning.

Code like this just cries out for comptime:

pub fn escSMsFromSIISMs(sii_sms: []const SyncM) esc.SMRegister {
    var res = std.mem.zeroes(esc.SMRegister);
    const fields = @typeInfo(SMRegister).Struct.fields;
    inline for (fields, 0..) |field, index| {
        if (sii_sms.len > index) {
            @field(res, field.name) = escSMFromSIISM(sii_sms[index]);
        } else break;
    }
    return res;
}

The typedef itself can be done in comptime, although it wouldn’t look too tidy:

pub const SMRegister = @Type(.{
    .Struct = .{
        .layout = .@"packed",
        .backing_integer = u2048,
        .fields = fields: {
            var list: [32]std.builtin.Type.StructField = undefined;
            for (&list, 0..) |*ptr, index| {
                ptr.* = .{
                    .name = std.fmt.comptimePrint("SM{d}", .{index}),
                    .type = SyncManagerAttributes,
                    .default_value = null,
                    .is_comptime = false,
                    .alignment = @alignOf(SyncManagerAttributes),
                };
            }
            break :fields &list;
        },
        .decls = &.{},
        .is_tuple = false,
    },
});

And soon .Struct will become .@"struct".

1 Like