You can hide fields too:
const PublicFoo = struct {
gadget: Gadget,
pub fn create(allocator: Allocator) !*PublicFoo {
var b_raw = try allocator.alloc(u8, @sizeOf(PublicFoo) + @sizeOf(PrivateBar);
var foo_ptr: *PublicFoo = @ptrCast(@alignCast(b_raw));
// Fill in public stuff
foo_ptr.gadget = .widget;
var bar_ptr: *PrivateBar = @ptrCast(@alignCast(b_raw[@sizeOf(PublicFoo)...]));
// Fill in private stuff
// ...etc
return foo_ptr;
}
// Add a little helper fn to get your private stash,
// remember to free it, and so on. Done.
};
I’ve done this, not to hide fields, but to have variable-sized allocations which are dense in memory. It works.
I find Zig’s publicity rules fairly pragmatic. Especially when working in teams, being able to separate the intended API from the details is a good way to stay out of trouble. For the most part, hiding data is not the same kind of useful, but if it is, there’s stuff like the above, and *anyopaque. It can be done.
I thought it would be nicer, at one point, if pub followed container scope, not file scope, so a bit stricter. Just for the consistency of it, or mostly.
Changed my mind about that. A file makes sense as the visibility boundary, because it’s literally everything which is visible. When working, I’m not going to notice the consistency: I would notice the futility of hiding things from myself, however.