So did you ever encounter the reason to implement some runtime type erasure and you couldn't figure out how to assign a unique number to a type?
Well, there' this one tiny trick Spex once told me:
pub fn typeId(comptime T: type) usize {
_ = T;
const H = struct {
var byte: u8 = 0;
};
return @ptrToInt(&H.byte);
}
This piece of code take any Zig type as a parameter and returns a unique ID for that type. This is done by generating a global variable each time we call the function. Due to memoization, we get the same result if we call the function with the same parameters again in the future, we have the guarantee that typeId(u32) == typeId(u32)
as well as typeId(u32) != typeId(u31)
in all cases.
I have implemented this feature in a library called any-pointer which also implements a type erased pointer that has runtime safety features in Safe modes, so you will get a panic instead of a type confusion error.
We can also play this further by enforcing the compiler to emit "sequential" type ids:
const TypeMap = struct {
const section_name = ".bss.TypeMapKeys"; // use a distinct section to ensure sequential linking
export const @"TypeMap.head": u8 linksection(section_name) = 0;
pub fn get(comptime T: type) usize {
_ = @"TypeMap.head"; // enforce referencing and instantiation of the head before we every export anything else
const Storage = struct {
const index: u8 linksection(section_name) = 0;
};
comptime {
@export(Storage.index, .{ .name = "TypeMap.index." ++ @typeName(T), .linkage = .Strong });
}
return @ptrToInt(&Storage.index) - @ptrToInt(&@"TypeMap.head");
}
};
This uses a hack that we create a custom section to put our type marker byte in. By forcing the export of a head
variable, we get a reference point for the sequence.
Note that this does not guarantee you anything except that we get a dense type id set that starts at most likely 1. If we employ a linker script, we can enforce the head
to be put at the start of the section to get the guarantee that type ids start at 1. I don't recommend do use this though, just use the sparsely distributed typeId
function if you need some RTTI.
Top comments (2)
I believe that starting with 0.12.0 you must capture the passed comptime parameters in order to ensure a distinct type is being generated, since memoization no longer plays a part in the semantics of the language.
I wonder if you have a link to a repo/project were you used this, to see it in action.