If your initialization code (where create
gets called) is not in a loop, you can use @src()
as your unique id, and it also has the upside of providing meaningful information when troubleshooting
@src()
evaluates to an instance of std.builtin.SourceLocation
(make sure to read what it contains, it’s necessary to understand the rest of my post). If you mark the argument as comptime then it should provide you with all you need.
So something along the lines of
fn create(comptime loc: std.builtin.SourceLocation, ...) {}
// usage
var foo = create(@src(), ...);
Note though that the uniqueness of @src()
breaks if create
is called in a loop, as multiple calls to it will share the same exact instance of SourceLocation
. If you really must initialize something in a loop, as a workaround you could create a var instance of SourceLocation
that you then modify to provide more uniqueness.
Here’s your script from the other thread changed to employ this technique (in other words I figured out how to make this work after failing initially):
const std = @import("std");
const print = std.log.info;
const CA = struct {
s: *S,
};
const CB = struct {
s: *S,
s2: *S2,
};
const S = struct {
x: u32 = 10,
};
const S2 = struct {
y: u32 = 5,
};
fn create(comptime T: type, comptime loc: std.builtin.SourceLocation) *T {
return &struct {
pub const id = loc;
pub var o = blk: {
break :blk std.mem.zeroInit(T, .{});
};
}.o;
}
fn initC(CT: type, comptime unique: [:0]const u8) CT {
comptime {
var new_c: CT = undefined;
const cti = @typeInfo(CT);
for (cti.Struct.fields, 0..) |fld, idx| {
const fti = @typeInfo(fld.type);
const FT = fti.Pointer.child;
var loc = @src();
loc.fn_name = unique;
loc.column = idx;
const fval = create(FT, loc);
@field(new_c, fld.name) = fval;
}
const res = new_c;
return res;
}
}
const ca = initC(CA, "unique-id-1");
const cb = initC(CB, "unique-id-2");
pub fn main() void {
ca.s.x = 20;
print("ca.s.x = {d}", .{ca.s.x});
print("cb.s.x = {d}", .{cb.s.x});
print("cb.2.y = {d}", .{cb.s2.y});
}
One thing to note about this code is that here there are two things that must be fixed to make the initialization code actually unique:
- the
initC
function is called from different locations so those locations need to provide a unique id of their own
- inside of its body,
initC
calls create
in a loop, so that must also be rendered unique
To solve these problems I’ve repurposed SourceLocation.fn_name
to be the unique invocation id, while SourceLocation.column
to be the loop iteration index.
Depending on how much looping there is in your final code, you will need either more logic than this, or less if you don’t have as much looping. For example the simplified version of the program that you shared in this post would require less logic.
One thing that woudl have made this solution simpler would have been the ability to do this:
const ca = initC(CA, @src());
const cb = initC(CB, @src());
As the two calls to src would have been naturally unique since those are two different lines, but unfortunately Zig currently doesn’t allow calling src outside of a function context.