Yeah, good point - forgot about that little detail. You can do this and it works:
const std = @import("std");
const print = std.log.info;
const S = struct {
x: u32 = 10,
};
fn create(T: type, comptime handle: anytype) *T {
return &struct {
var o = blk: {
std.mem.doNotOptimizeAway(handle);
break :blk std.mem.zeroInit(T, .{});
};
}.o;
}
pub fn main() void {
var vp = create(S, opaque{});
const cp = create(S, opaque{});
vp.x = 20;
cp.x = 30;
print("vp.x = {d}", .{vp.x});
print("cp.x = {d}", .{cp.x});
}
I forgot that if you don’t use the argument, it gets discarded and it can get folded into one instance. In this case, we are effectively throwing it away, but in a way that is opaque to the compiler.
Also, you asked if there is any motivation to this style of function. One affordance is that we can get the pointer through a pointer instance or we can re-reference the same thing using the same handle. This is well behaved:
pub fn main() void {
var vp = create(S, 0);
const cp = create(S, 1);
vp.x = 20;
cp.x = 30;
print("vp.x = {d}", .{vp.x});
print("cp.x = {d}", .{cp.x});
print("(0).x = {d}", .{create(S, 0).x});
print("(1).x = {d}", .{create(S, 1).x});
}
So here, I can get the same struct again using my key value 0
or 1
(or any other comptime known unique value). It’s an interesting detail that may be worth something (you could achieve something similar in other ways, too).