hizani
July 13, 2025, 3:50pm
1
I have a code that generates IDs for types in comptime.
const ComponentID = comptime_int;
const component_identifier = block_name: {
comptime var last_id = 0;
const result = struct {
fn getComponentTypeIDnum(comptime T: type) ComponentID {
_ = T;
last_id += 1;
return last_id;
}
pub fn getComponentTypeID(comptime T: type) ComponentID {
const S = struct {
const type_id = getComponentTypeIDnum(T);
};
return S.type_id;
}
};
break :block_name result;
};
test "static" {
const std = @import("std");
const a = component_identifier.getComponentTypeID(i32);
const b = component_identifier.getComponentTypeID(i32);
const c = component_identifier.getComponentTypeID(usize);
const d = component_identifier.getComponentTypeID(isize);
const e = component_identifier.getComponentTypeID(f32);
const TestStruct = struct {};
const f = component_identifier.getComponentTypeID(TestStruct);
try std.testing.expect(a == 1);
try std.testing.expect(b == 1);
try std.testing.expect(c == 2);
try std.testing.expect(d == 3);
try std.testing.expect(e == 4);
try std.testing.expect(f == 5);
}
It compiles with Zig v0.11 but doesn’t with later versions.
Is there a way to have the same logic in v0.14?
Hi and welcome to the forum!
Here’s one approach: https://github.com/ziglang/zig/issues/19858#issuecomment-2369861301
The code from that comment:
const TypeId = *const struct {
_: u8,
};
pub inline fn typeId(comptime T: type) TypeId {
return &struct {
comptime {
_ = T;
}
var id: @typeInfo(TypeId).pointer.child = undefined;
}.id;
}
2 Likes
Could you abuse errors for this?
const id = @intFromError(error.StructName);
The linked issue mentions a variation of that in a comment, but I’d just use what the core team recommends (the snippet above)
hizani
July 13, 2025, 7:39pm
5
I’ve came up with this solution, if someone needs an integer representation of a type.
const std = @import("std");
const TypeId = comptime_int;
// FNV-1a 64-bit hash
fn typeNameToId(comptime T: type) TypeId {
const name = @typeName(T);
var hash: u64 = 0xcbf29ce484222325;
inline for (name) |c| {
hash ^= c;
hash *%= 0x100000001b3;
}
return @as(TypeId, hash);
}
pub inline fn typeId(comptime T: type) TypeId {
return struct {
const id = typeNameToId(T);
}.id;
}
test "hashed type id" {
const id1 = typeId(i32);
const id2 = typeId(i32);
const id3 = typeId(usize);
const TestStruct = struct {};
const id4 = typeId(TestStruct);
const id5 = typeId(TestStruct);
const TestStruct2 = struct {};
const id6 = typeId(TestStruct2);
try std.testing.expect(id1 == id2);
try std.testing.expect(id1 != id3);
try std.testing.expect(id4 == id5);
try std.testing.expect(id4 != id6);
}
It has an astronomically small chance of collision tho
1 Like
Comptime-known string values are deduplicated so there’s only one of any given string. So a static pointer to the type name could be used as a type ID. Advantage: won’t collide, can retrieve the type name, it’s null-terminated so it can be slice-ified if necessary. Disadvantage: the code has a bunch of type name strings, which maybe it doesn’t need.
If the latter isn’t a problem this seems like the simplest solution. Type names are qualified by namespace so synonyms aren’t a problem.
1 Like