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.
pub fn typeId(comptime T: type) usize {
const H = struct {
var byte: u8 = 0;
var _ = T;
};
return @intFromPtr(&H.byte);
}
This is the most concise implementation I’ve seen yet, however it still has the (subjective) drawback of having the IDs clustered around wherever these structs happen to get loaded into memory.
Here is a version of the sample that takes advantage of link sections to get comptime ordered counting starting at 1:
This is pretty dodgy, but it actually works surprisingly well. Initially I tried to have the header and the type entries in the same section (closer to the example in the article), but I was running into issues with the header always being added to the symbol table after the first type was added. This meant that the first type I’d register would have the unsigned equivalent of -1 as its ID, and then each subsequent type registered would increment correctly from 1.
The workaround to this problem is to rely on the symbol tables being loaded in lexicographic order, but I can’t say with any confidence that this is a portable technique.