Type id comptime generation

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)

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