Zig equivalent to C idiom for structure with "tail" data

The idea is essentially:

allocate @sizeOf(Struct) + trailing_size bytes with alignment @alignOf(Struct), cast it to *Struct, and then make sure that you can still free the entire allocated slice (so either store the size of the trailing data in the header, or make it so the size of the trailing data can be calculated when it’s time to free).

Here’s an example (from this help thread in Discord):

const std = @import("std");

pub const Il2CppString = extern struct {
    len: usize,
    // 0-length dummy field for trailing UTF-16 data
    data: [0]u16,

    pub fn getData(self: *Il2CppString) []u16 {
        return @as([*]u16, &self.data)[0..self.len];
    }

    pub fn init(ally: std.mem.Allocator, s: []const u8) !*Il2CppString {
        const utf16_len = try std.unicode.calcUtf16LeLen(s);
        const full_byte_len = @sizeOf(Il2CppString) + (utf16_len * 2);
        var string_bytes = try ally.alignedAlloc(u8, @alignOf(Il2CppString), full_byte_len);
        var string = @as(*Il2CppString, @ptrCast(string_bytes.ptr));
        string.* = .{
            .len = utf16_len,
            .data = undefined,
        };
        var data_slice = @as([*]u16, @ptrCast(&string.data))[0..utf16_len];
        // catch unreachable since we already know `s` is valid UTF-8 from calcUtf16LeLen above
        _ = std.unicode.utf8ToUtf16Le(data_slice, s) catch unreachable;
        return string;
    }

    pub fn deinit(self: *Il2CppString, ally: std.mem.Allocator) void {
        const byte_len = @sizeOf(Il2CppString) + self.len * 2;
        const byte_slice = @as([*]align(@alignOf(Il2CppString)) u8, @ptrCast(self))[0..byte_len];
        ally.free(byte_slice);
    }
};

test Il2CppString {
    var string = try Il2CppString.init(std.testing.allocator, "hello");
    defer string.deinit(std.testing.allocator);

    const hello_utf16 = std.unicode.utf8ToUtf16LeStringLiteral("hello");
    try std.testing.expectEqualSlices(u16, hello_utf16, string.getData());
}

Here’s a more complicated example from the standard library: Objects with header first and payload after? - #13 by squeek502

5 Likes