this is the result of my blog post titled Resizable structs in Zig. as it were, these structs are not resizable.
I'm told there's several places in the standard library where a helper utility like this would make the code easier to maintain, so I am contributing this upstream.
Creating a flexible struct
Declare a type using FlexibleStruct by passing in a struct that has fields of type FlexibleArray:
constPacket=FlexibleStruct(struct{host_len:usize,host:FlexibleArray(u8,.host_len),buf_lens:u32,read_buf:FlexibleArray(u8,.buf_lens),write_buf:FlexibleArray(u8,.buf_lens),});
the FlexibleArray fields are zero-sized markers. they accept an element type and an enum literal pointing toward the field of the parent struct that contains its length. the type of that length field can be any integer type.
length fields can also be shared between flexible array members.
there are two ways to work with this meta type: via an allocation on the heap, or via a stack held buffer.
Allocation
There is a create/destroy method pair that allocates a buffer on the heap, casts its pointer to *YourType, and sets the length fields:
constpacket:*Packet=try.create(testing.allocator,.{.host_len=host.len,.buf_lens=10,});deferpacket.destroy(testing.allocator);
Stack Buffer
You can either just use a [some_size]u8 buf, or use the helper type that sizes an array such that the buffer can hold the desired capacity:
varbuf:Packet.Buf(.{.host_len=1024,.buf_lens=128,})=undefined;constpacket:*Packet=.initBuffer(&buf,.{.host_len=host.len,.buf_lens=10,});
Using a flexible struct
Once you have one, whether on the stack or heap, the API is quite minimal:
ptr(field_tag) returns a pointer to a field
len(field_tag) returns the array length of a field
slice(field_tag) returns the slice of that field
Notes
Alignment
I've done my best to make sure fields/allocations are properly aligned, but might have missed something.
Layout
The underlying layout of the buffer is well defined when you provide a layout struct that is well defined (it is extern). When you provide an auto layout struct, the layout is not well defined (they are sorted by alignment).
Concretely, this is demonstrated in the test case:
test"layout"{constwell_defined=FlexibleStruct(externstruct{len:u8,arr:FlexibleArray(u64,.len),});constnot_well_defined=FlexibleStruct(struct{len:u8,arr:FlexibleArray(u64,.len),});trytesting.expectEqual(16,well_defined.calcSize(.{.len=1}));trytesting.expectEqual(9,not_well_defined.calcSize(.{.len=1}));}
this is the result of my blog post titled [Resizable structs in Zig](https://tristanpemble.com/resizable-structs-in-zig/). as it were, these structs are not resizable.
I'm told there's several places in the standard library where a helper utility like this would make the code easier to maintain, so I am contributing this upstream.
## Creating a flexible struct
Declare a type using `FlexibleStruct` by passing in a struct that has fields of type `FlexibleArray`:
```zig
const Packet = FlexibleStruct(struct {
host_len: usize,
host: FlexibleArray(u8, .host_len),
buf_lens: u32,
read_buf: FlexibleArray(u8, .buf_lens),
write_buf: FlexibleArray(u8, .buf_lens),
});
```
the `FlexibleArray` fields are zero-sized markers. they accept an element type and an enum literal pointing toward the field of the parent struct that contains its length. the type of that length field can be any integer type.
length fields can also be shared between flexible array members.
there are two ways to work with this meta type: via an allocation on the heap, or via a stack held buffer.
### Allocation
There is a `create`/`destroy` method pair that allocates a buffer on the heap, casts its pointer to `*YourType`, and sets the length fields:
```zig
const packet: *Packet = try .create(testing.allocator, .{
.host_len = host.len,
.buf_lens = 10,
});
defer packet.destroy(testing.allocator);
```
### Stack Buffer
You can either just use a `[some_size]u8` buf, or use the helper type that sizes an array such that the buffer can hold the desired capacity:
```zig
var buf: Packet.Buf(.{
.host_len = 1024,
.buf_lens = 128,
}) = undefined;
const packet: *Packet = .initBuffer(&buf, .{
.host_len = host.len,
.buf_lens = 10,
});
```
### Using a flexible struct
Once you have one, whether on the stack or heap, the API is quite minimal:
- `ptr(field_tag)` returns a pointer to a field
- `len(field_tag)` returns the array length of a field
- `slice(field_tag)` returns the slice of that field
## Notes
### Alignment
I've done my best to make sure fields/allocations are properly aligned, but might have missed something.
### Layout
The underlying layout of the buffer is well defined when you provide a layout struct that is well defined (it is extern). When you provide an auto layout struct, the layout is not well defined (they are sorted by alignment).
Concretely, this is demonstrated in the test case:
```zig
test "layout" {
const well_defined = FlexibleStruct(extern struct {
len: u8,
arr: FlexibleArray(u64, .len),
});
const not_well_defined = FlexibleStruct(struct {
len: u8,
arr: FlexibleArray(u64, .len),
});
try testing.expectEqual(16, well_defined.calcSize(.{ .len = 1 }));
try testing.expectEqual(9, not_well_defined.calcSize(.{ .len = 1 }));
}
```