I can think of two potential approaches to this. Both use the uniqueness of anonymous opaque {}
types, which has also been (ab)used for other interesting purposes in the past.
The first one is slightly less hacky, but has the major disadvantages that 1. the returned value is not comptime-known and 2. it wastes one byte of space for each counter value in the final binary:
const std = @import("std");
pub fn counter(comptime T: type) usize {
if (@typeInfo(T) != .Opaque or @typeInfo(T).Opaque.decls.len != 0) {
@compileError("pass opaque {} as the argument");
}
return @intFromPtr(&struct {
var t: u8 = 0;
}.t);
}
pub fn main() !void {
std.debug.print("{}\n", .{counter(opaque {})});
std.debug.print("{}\n", .{counter(opaque {})});
}
(as described in [Feature Request] @typeInfo of a `type` with a unique identifier · Issue #5459 · ziglang/zig · GitHub; the linked issue being solved would make this more natural)
The second approach is more reliant on implementation details, but it does work at comptime and should not waste any space:
const std = @import("std");
pub fn counter(comptime T: type) comptime_int {
if (@typeInfo(T) != .Opaque or @typeInfo(T).Opaque.decls.len != 0) {
@compileError("pass opaque {} as the argument");
}
const type_name = @typeName(T);
const qualifier_index = std.mem.lastIndexOfScalar(u8, type_name, '_') orelse
@compileError("pass opaque {} as the argument");
var value: comptime_int = 0;
for (type_name[qualifier_index + 1 ..]) |c| {
value = 10 * value + (@as(comptime_int, c) - '0');
}
return value;
}
const a = counter(opaque {});
const b = counter(opaque {});
pub fn main() !void {
std.debug.print("{}\n", .{a});
std.debug.print("{}\n", .{b});
std.debug.print("{}\n", .{counter(opaque {})});
std.debug.print("{}\n", .{counter(opaque {})});
}