Defined memory layout separate from a C ABI

The following code doesn’t work

17:07:47|~/tmp
λ bat main.zig 
pub const S = extern struct {
    x: u256,
};

pub fn f(s: S) void {
    _ = s;
}

pub fn main() void {
    f(.{ .x = 92 });
}


17:08:02|~/tmp
λ zig build-exe main.zig
main.zig:2:8: error: extern structs cannot contain fields of type 'u256'
    x: u256,
       ^~~~
main.zig:2:8: note: only integers with 0, 8, 16, 32, 64 and 128 bits are extern compatible

This makes sense C doesn’t support u256 yet, so there isn’t an ABI which could be used to pass this struct between C and Zig. But what if I don’t care about C ABI per se, and only need a well-defined memory layout (eg, I am describing the data as it exists on the disk / on the network)? Is there a way for me to make a bit-castable Zig struct with an u256 field and a well-defined memory layout, without, at the same time, guaranteeing a particular ABI for cross-language interop?

2 Likes

You’re looking for packed.

I don’t think packed quite cuts it here: I don’t want my bools to turn into u1s, for example.

2 Likes

Why would your bool turn into u1?

const std = @import("std");

pub const S = packed struct {
    _: u256 = 0,
    a: bool,
};

pub fn f(s: S) void {
    _ = s;
}

pub fn main() void {
    const s: S = .{.a = true};
    f(s);
    std.debug.print("{any}\n", .{s});
}
output: example.S{ ._ = 0, .a = true }

The bool is still a bool. If you’re saying that you don’t want the bool to occupy a single bit, just add whatever padding you need.

pub const S = packed struct {
    _: u256 = 0,
    a: bool,
    padding: u7 = undefined,
};

Now for external code, it will look as if the bool is occupying 8 bits.

1 Like

But depending on your endianness it could be a 8-bit bool but with the wrong bit set.

Yes, when working at such low level, you do need to take into consideration endianness. But the idea was to work with, for instance, a network protocol. The protocol would specify which bit would be the one you’re looking for, so you just define your struct so that the bool matches whatever the protocol specifies.

Here comes the trouble. Speaking of network protocols, TCP/IP requires big-endian while most popular archs (x86, x86-64, aarch64) are little-endian. An explicit conversion is needed. Doing it for arbitrary packed struct is no fun.