Description
So, everyone knows the problems we have with packed structs
. Some are implementation problems, but we also have a huge problem with not having a proper specification on how they should work at all, except for "zero padding".
So i want to propose a definition for packed struct
s that is easy to understand and implement.
Let's consider this code example:
const T = packed struct {
a: u4,
b: u4,
};
var bits = [_]u8 { 0x01 };
var t = @bitCast(T, bits);
std.debug.print("{}\n", .{ t });
What do you expect it to print?
Is it T{ .a = 1, .b = 0 }
? Then you have assumed a little-endian platform, as on big endian, it will print T{ .a = 0, .b = 1 }
.
Check it out on Compiler Explorer.
Personally, i find this confusing, thus i propose:
A packed struct will have a similar semantic as a unsigned integer with the same amount of bits. Each field is considered an unsigned integer of the same amount of bits.
To explain the idea, we have this struct:
const T1 = packed struct { // u8
a: u4, // occupies bits 0..3
b: u4, // occupies bits 4..7
};
const T2 = packed struct { // u32
a: u4, // occupies bits 0..3 in our u32
b: u3, // occupies bits 4..6 in our u32
c: u25, // occupies bits 7..31 in our u32
};
So the packed struct implements exactly what we would do in a language without packed structs:
var value: T2 = …;
var a = @truncate( u4, 0x0000000F & (value >> 0));
var b = @truncate( u3, 0x00000007 & (value >> 4));
var c = @truncate(u25, 0x01FFFFFF & (value >> 7));
This means that we have a predictable model for backed structs and in case of T2
, we can reason about it:
const integer: u32 = 0x165652B6;
const value = @bitCast(T2, integer);
std.debug.assert(value.a == 6);
std.debug.assert(value.b == 3);
std.debug.assert(value.c == 0x2CACA5);
This will be true for both little and big endian hardware, so it will do what we expect, even if the bytes on disk have a different order.
If a known byte order is required, we can use std.mem.writeIntLittle
, @byteSwap
and others.
In addition, we can declare a struct as packed struct(u32)
, which will enforce the struct size to 32 bit, and we will get a compiler error if it isn't the case.
This will also guarantee that this struct is handled as a u32
and can be used as such, similar to enums. This also guarantees that loads and stores will not be sliced into more than one access if possible and allows atomic load/store for such packed structs. (See #5049).
What needs to be clarified:
- How to handle floats (float endianess is a thing)
- How to handle pointers in a packed struct
- How to handle pointers to struct fields
Related issues:
- make packed structs versatile #3133
- Incorrect byte offset and struct size for packed structs #2627
- Proposal: Integer-backed packed struct #5049
Regards
- xq