Search code examples
zig

Create a HashMap with a Key of Struct with Slices in Zig


How do I use a custom struct with multiple string keys as a Key an an ArrayHashMap? Using the Auto variant here I get the following error:

const MyKey = struct {
    part_one: [] const u8,
    part_two: [] const u8,
    allocator: ?std.mem.allocator,

    //...
}

const MappingType = std.AutoArrayHashMap(MyKey, u32);
/nix/store/wrm7r7msb7yrhnwaxgbpn2mc7kla6x4a-zig-0.11.0/lib/zig/std/hash/auto_hash.zig:199:9: error: std.hash.autoHash does not allow slices as well as unions and structs containing slices here (MyKey) because the intent is unclear. Consider using std.hash.autoHashStrat or providing your own hash function instead.
        @compileError("std.hash.autoHash does not allow slices as well as unions and structs containing slices here (" ++ @typeName(Key) ++
        ^~~~~~~~~~~~~

I've tried looking up how to make my own Context with ArrayHashMap but I can't seem to figure it out. I see the below in ArrayHashMap:

/// Context must be a struct type with two member functions:
///   hash(self, K) u32
///   eql(self, K, K, usize) bool
  • I presume for hash I could just use a hashing function in std.hash and return the value (I don't need to compare the allocator)?

  • The eql here I presume means to check if two keys are equal? But then what is usize used for? I can see the getAutoEqlFn ignores this usize called b_index - but is this necessary at all?

  • Or is there a much easier way of doing this?

Thanks in advance


Solution

  • You are trying to use a custom struct MyKey with string slices as keys in std.AutoArrayHashMap in Zig, but encountering an error due to the inability of std.hash.autoHash to handle slices in structs.

    To solve this, you would need to implement a custom hash and equality function for your struct, and then use these in a context struct for ArrayHashMap.

    Define struct:

    const MyKey = struct {
        part_one: []const u8,
        part_two: []const u8,
        allocator: ?std.mem.Allocator,
        //...
    };
    

    Now You need to define the context structure as below

    const MyKeyContext = struct {
        fn hash(ctx: MyKeyContext, key: MyKey) u32 {
            var h = std.hash.Fnv32a.init();  // <- change the hash algo according to your needs... (WyHash...)
            h.update(key.part_one);
            h.update(key.part_two);
            return h.final();
        }
    
        fn eql(ctx: MyKeyContext, a: MyKey, b: MyKey, _: usize) bool {
            return std.mem.eql(u8, a.part_one, b.part_one) && std.mem.eql(u8, a.part_two, b.part_two);
        }
    };
    

    Usage:

    const MappingType = std.ArrayHashMap(MyKey, u32, MyKeyContext);
    var allocator = std.heap.page_allocator;
    var map = MappingType.init(&allocator);
    defer map.deinit();
    var key = MyKey{ .part_one = "key1", .part_two = "key2", .allocator = allocator };
    map.put(key, 42) catch unreachable;