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.
Edit: Correction: the id generation is still runtime, because the pointers are not comptime known (which makes sense since I guess they are laid out at link time which happens after), therefore this code still needs to perform two memory loads and a subtraction at runtime. It’s probably ok in pactice, but still.
It looks like you can do away with only one linksection as long as you force the compiler to load its address before creating the helper struct. I’ve also noticed that at least on my machine and godbolt the linksection thus created has an alignment of 16 bytes, but align(x) can be used to control it.
This program works on linux to obtain monotonically increasing typeIds at compile time (link time?). Notice how const start = &tid_section; goes before const H, otherwise you run into the issue you described where the first typeId is the unsigned equivalent of -1.
I don’t know if this is subject to breakage due to potential compiler optimization, but at the very least in zig 0.15.2 it works correctly in every configuration (Debug, ReleaseSmall, ReleaseSafe, ReleaseFast, with or without llvm backend).