Comptime array generation

I’ve been writing some zig in the past, including comptime stuff, and find it generally quite approachable. But here is something I can’t seem to get my head around, maybe one of you fine people has an idea. I basically want to create something like a simple dictionary at comptime, because it is to be created out of static strings. The basic idea is this: I have a function that I call with a comptime known array of comptime known strings strings, and out comes a structure containing an array of structs containing these strings and additional stuff. Kinda like so:

fn MakeDict(.{
  "string1",
  "somestuff",
  "wuppie",
  ...
})

and I want to get sth like this out:

.{
  .{ hash = makehash("string1"), .len = 7, str = "string1" },
  .{ hash = makehash("somestuff"), .len = 9, str = "somestuff" },
  ...
}

where the individual structs have an array at the end, not a hash. Something like

struct {
  hash: usize,
  len: usize,
  str: [_]u8
}

(which obvsly does not work like that). As the strings are quite short (let’s say, no more than 32 bytes), I could live with a fixed size for str, However, I don’t even have a proper idea on how to approach this. Is this even possible? I would be grateful for any insight.

I think your post got cut off there. Anyway, have you seen StaticStringMap (ComptimeStringMap in previous versions)? Here’s a link to the start of the tests which demonstrate usage.

3 Likes

It is possible:

const std = @import("std");

fn DictType(comptime names: anytype) type {
    var fields: [names.len]std.builtin.Type.StructField = undefined;
    inline for (names, 0..) |name, index| {
        const T = struct {
            comptime hash: u64 = std.hash.cityhash.CityHash64.hash(name),
            comptime name: [name.len]u8 = name.*,
        };
        fields[index] = .{
            .name = std.fmt.comptimePrint("{d}", .{index}),
            .type = T,
            .default_value = @ptrCast(&T{}),
            .is_comptime = true,
            .alignment = @alignOf(T),
        };
    }
    return @Type(.{ .Struct = .{
        .layout = .auto,
        .fields = &fields,
        .decls = &.{},
        .is_tuple = true,
    } });
}

const dictionary: DictType(.{
    "evil_clown",
    "donuts",
    "free_beer",
    "antimatter_snowball",
}) = .{};

pub fn main() void {
    inline for (dictionary) |entry| {
        std.debug.print("{any}\n", .{entry});
    }
}

That’d leave you with a comptime-only tuple though. You would always have to go through the entries using inline loops (since each has a different struct type). My guess is that you actually want to use a slice for the name, which makes things much simpler:

const std = @import("std");

const DictEntry = struct {
    hash: u64,
    name: []const u8,
};

fn makeDict(comptime names: anytype) [names.len]DictEntry {
    var entries: [names.len]DictEntry = undefined;
    inline for (names, 0..) |name, index| {
        entries[index] = .{
            .hash = std.hash.cityhash.CityHash64.hash(name),
            .name = name,
        };
    }
    return entries;
}

const dictionary = makeDict(.{
    "evil_clown",
    "donuts",
    "free_beer",
    "antimatter_snowball",
});

pub fn main() void {
    for (dictionary) |entry| {
        std.debug.print("{any}\n", .{entry});
    }
}

Now dictionary can exist in runtime.

3 Likes

In addition to all the good answers you got here, I wanted to suggest using an enum, if it’s ok for the dictionary strings to conform to Zig identifier rules.

You can use those as tags for a tagged union, and if you need the name as a slice you can use @tagName to get that, and go the other direction with std.meta.stringToEnum, which will build a StaticStringMap for you if there’s less than 100 enums (more will make an unrolled for loop). Even if all the structs have the same structure, meaning you wouldn’t need a tagged union, enums have some advantages.

It’s not clear whether this is the right choice, since I don’t know what you’re trying to do with this data structure. But enums have several advantages, including a compact representation, fast comparisons, and exhaustive switching. If an enum will work, you should probably use one.

2 Likes

Thanks for the very nice ideas. Not all of them are fitting my current use case, but I’ll keep all of them in mind for the future. It’s a pity that I can’t mark multiple posts as solutions :slight_smile: For now, Chung-Leongs answer most closely fits what I am trying to do.