Memory layout of embedded struct

Let’s say I have these structs:

const A = extern struct {
  foo: u32,
  bar: u64,
};

const B = struct {
  a: A,
  
  pub fn init(foo: u32, bar: u64) B {
    return .{ .a = .{ .foo = foo, .bar = bar };
  }
};

Could I @bitCast B to A?

There is a case where I need to keep an external layout, but I’d like to add a helper for constructing it in a cross-platform way. Specifically it’s for iovec vs WSABUF. I’d like to have .fromSlice() method, but internally I need to be able to bitcast it, or rather ptrcast the pointer to an array of such structs.

I’m pretty sure that’s illegal behavior, the langref states:

// Zig gives no guarantees about the order of fields and the size of
// the struct but the fields are guaranteed to be ABI-aligned.

So even if this works today, it’s perfectly ok for the Zig compiler to add safety related fields or whatever to B in the future.

Why can you not just make B extern as well? I think that would be the easiest solution.

3 Likes

Maybe adding extern to B too would help because I think it guarantees compatibility with C ABI. I have not used this in C myself but I have heard it being used in this talk so I would assume it works consistently. https://youtu.be/443UNeGrFoM?si=OTZHwVcbu5zLJ7wb&t=4275

If I were you I would mark everything extern, assume @bitCast works and add the following block in the code:
comptime {
if (@sizeOf(A) != @sizeOf(B)) @compileError(“struct A must be the same size as struct B”);
}
This way if your code doesn’t work on a platform you know immediately and can act accordingly. You could also edit this block to add more checks for anything “fishy” you plan to do.

1 Like

Yep, I forgot that I can have methods on extern structs. That, with the comptime asserts, fixes all the ambiguity.

Regardless, bit casting and pointer casting are redundant, as you can just access the field b.a.

You can also get a pointer to a length 1 array from a single item pointer (&b.a)[0..1], ofc it needs to be comp time known to be 0..1.

1 Like

I need to convert []B to []A for syscalls. The single pointer arithmetic has the same prohlem if the memory layout is not guaranteed.

ah, you already have a slice of B. Then I would say, pointer casting is the most ergonomic, assuming B only contains an A. That requirement should be documented in B.

But I do question why you have B at all, why not just use A directly?

I assume, either, A is from some library you are using, or/and, A is a lower level primitive and B is an abstraction over different potential lower level primitives, in which case the only reason to have B is to call the functions on an instance b.foo() as opposed to foo(a), without having to implement almost identical code for each A.

I would prefer foo(a) to avoid pointer casting and keep type safety.

or maybe something else?