Error with HashMap where the key is a struct containing a pointer to another struct

Hi. I am playing around with HashMap in zig. In this file, I try to use HashMaps to keep track of packages. Each package is single file named as <author>.<name>.<version>.ext. author and name are strings while version is an unsigned integer. I have created two structs to represent a package, one is Package which contains the author and name. The other is PackageVersioned containing a pointer to the corresponding Package and the version.

I record all Packages I see in PackageMap thus ensuring that a single Package (name and author combination) is not duplicated. Then for the PackageVersioned I try to get the pointer to the corresponding Package from PackageMap then use it to construct a PackageVersioned then shove it into the PackageVersionedMap.

I know this is a bit stupid but this is mostly an exercise to wrap my head around working with pointers in this scenario. If I make PackageVersioned contain a copy of Package rather than a pointer to it, it works flawlessly. However when I use a pointer I get this error that I don’t understand. I was hoping someone here could take a look and tell me what mistake I have made.

The code is a single zig file I name hmap_test.zig:

hmap_test.zig
const std = @import("std");

//
// Package struct stuff BEGIN
//
const Package = struct {
    author: []const u8,
    name: []const u8,
};

const PCtx = struct {
    const Self = @This();
    pub fn eql(_: Self, s1: Package, s2: Package) bool {
        return std.mem.eql(u8, s1.author, s2.author) and std.mem.eql(u8, s1.name, s2.name);
    }
    pub fn hash(_: Self, s: Package) u64 {
        var wyhash = std.hash.Wyhash.init(0);
        wyhash.update(s.author);
        wyhash.update(s.name);
        return wyhash.final();
    }
};

const PackageMap = std.HashMap(
    Package,
    void,
    PCtx,
    std.hash_map.default_max_load_percentage,
);
//
// Package struct stuff END
//

//
// PackageVersioned struct stuff BEGIN
//
const PackageVersioned = struct {
    package: *const Package, // This is now a pointer to TestStruct1
    version: u8,
};

const PVCtx = struct {
    const Self = @This();
    pub fn eql(_: Self, s1: PackageVersioned, s2: PackageVersioned) bool {
        return (std.mem.eql(u8, s1.package.author, s2.package.author) and
            std.mem.eql(u8, s1.package.name, s2.package.name) and
            s1.version == s2.version);
    }
    pub fn hash(_: Self, s: PackageVersioned) u64 {
        var wyhash = std.hash.Wyhash.init(0);
        wyhash.update(s.package.author);
        wyhash.update(s.package.name);
        wyhash.update(std.mem.asBytes(&s.version));
        return wyhash.final();
    }
};

const PackageVersionedMap = std.HashMap(
    PackageVersioned,
    std.ArrayList([]const u8),
    PVCtx,
    std.hash_map.default_max_load_percentage,
);
//
// PackageVersioned struct stuff END
//

/// This function checks if the author, name combination exists in map1. If not it is
/// `put`. Then the pointer to the Package key having the corresponding author and name
/// is returned.
fn registerPackage(map1: *PackageMap, author: []const u8, name: []const u8) !*Package {
    const s = Package{ .author = author, .name = name };
    if (!map1.contains(s)) {
        try map1.put(s, {});
    }
    return map1.getKeyPtr(s).?;
}

/// This function first gets a `*Package `using `registerPackage`. Then it checks if
/// the combination of `*Package` and `version` exists in map2. If not, it is `put`
/// as the key with the value being an empty ArrayList. Then the path is appended
/// to this ArrayList.
fn registerPackageVersioned(
    map1: *PackageMap,
    map2: *PackageVersionedMap,
    author: []const u8,
    name: []const u8,
    version: u8,
    value: []const u8,
) !void {
    const s1_ptr = try registerPackage(map1, author, name);
    const s2 = PackageVersioned{ .package = s1_ptr, .version = version };
    if (!map2.contains(s2)) {
        try map2.put(s2, std.ArrayList([]const u8).init(map2.allocator));
    }
    var list = map2.getPtr(s2).?;
    try list.append(value);
}

pub fn main() !void {
    var arena = std.heap.ArenaAllocator.init(std.heap.page_allocator);
    defer arena.deinit();
    const allocator = arena.allocator();

    var map1 = PackageMap.init(allocator);
    defer map1.deinit();
    var map2 = PackageVersionedMap.init(allocator);
    defer map2.deinit();

    // Just register a bunch of packages. No dupes as of now.
    try registerPackageVersioned(&map1, &map2, "Sharon", "Penamouth", 14, "Sharon.Penamouth.14.ext");
    try registerPackageVersioned(&map1, &map2, "Travis", "Jerryburgh", 1, "Travis.Jerryburgh.1.ext");
    try registerPackageVersioned(&map1, &map2, "Nicole", "Newmanmouth", 3, "Nicole.Newmanmouth.3.ext");
    try registerPackageVersioned(&map1, &map2, "Brittany", "West Karenfurt", 10, "Brittany.West Karenfurt.10.ext");
    try registerPackageVersioned(&map1, &map2, "Karen", "Bushview", 20, "Karen.Bushview.20.ext");
    try registerPackageVersioned(&map1, &map2, "Adam", "South Michael", 10, "Adam.South Michael.10.ext");
    try registerPackageVersioned(&map1, &map2, "James", "West Derrickborough", 6, "James.West Derrickborough.6.ext");
    try registerPackageVersioned(&map1, &map2, "William", "Reynoldsville", 13, "William.Reynoldsville.13.ext");
    try registerPackageVersioned(&map1, &map2, "Andrew", "East Alyssaton", 10, "Andrew.East Alyssaton.10.ext");
    try registerPackageVersioned(&map1, &map2, "Ashley", "Port Hailey", 12, "Ashley.Port Hailey.12.ext");
    try registerPackageVersioned(&map1, &map2, "Jessica", "North Mary", 7, "Jessica.North Mary.7.ext");
    try registerPackageVersioned(&map1, &map2, "Lori", "Johnsport", 1, "Lori.Johnsport.1.ext");
    try registerPackageVersioned(&map1, &map2, "Cody", "Mcdanielstad", 19, "Cody.Mcdanielstad.19.ext");
    try registerPackageVersioned(&map1, &map2, "Megan", "Lake Kenneth", 15, "Megan.Lake Kenneth.15.ext");
    try registerPackageVersioned(&map1, &map2, "Steven", "Port Stacyville", 8, "Steven.Port Stacyville.8.ext");
    try registerPackageVersioned(&map1, &map2, "Bradley", "New Patricia", 7, "Bradley.New Patricia.7.ext");
    try registerPackageVersioned(&map1, &map2, "Lance", "West Craigfort", 3, "Lance.West Craigfort.3.ext");
    try registerPackageVersioned(&map1, &map2, "Courtney", "Lake Alexandrashire", 8, "Courtney.Lake Alexandrashire.8.ext");
    try registerPackageVersioned(&map1, &map2, "Julie", "Danielport", 2, "Julie.Danielport.2.ext");
    try registerPackageVersioned(&map1, &map2, "Christopher", "Jimenezshire", 17, "Christopher.Jimenezshire.17.ext");
    try registerPackageVersioned(&map1, &map2, "Anthony", "West Andrea", 20, "Anthony.West Andrea.20.ext");

    // Print a human readable representation of our package database
    var iter = map2.iterator();
    while (iter.next()) |entry| {
        std.debug.print(
            "[{s}, {s}, {}]: [ ",
            .{
                entry.key_ptr.package.author,
                entry.key_ptr.package.name,
                entry.key_ptr.version,
            },
        );
        for (entry.value_ptr.items) |item| {
            std.debug.print("{s}, ", .{item});
        }
        std.debug.print("]\n", .{});
    }
}

test "hash functions" {
    const allocator = std.testing.allocator;
    const author1 = "author1";
    const author1_dupe1 = try allocator.dupe(u8, author1);
    defer allocator.free(author1_dupe1);
    const author1_dupe2 = try allocator.dupe(u8, author1);
    defer allocator.free(author1_dupe2);
    std.debug.assert(&author1_dupe1 != &author1_dupe2);

    const ctx1 = PCtx{};
    const p1 = Package{ .author = author1_dupe1, .name = "name1" };
    const p2 = Package{ .author = author1_dupe2, .name = "name1" };
    const p3 = Package{ .author = "author2", .name = "name2" };
    std.debug.assert(&p1 != &p2);
    std.debug.assert(ctx1.hash(p1) == ctx1.hash(p2));
    std.debug.assert(ctx1.eql(p1, p2));
    std.debug.assert(ctx1.hash(p1) != ctx1.hash(p3));

    const ctx2 = PVCtx{};
    const pv1 = PackageVersioned{ .package = &p1, .version = 1 };
    const pv2 = PackageVersioned{ .package = &p2, .version = 1 };
    const pv3 = PackageVersioned{ .package = &p2, .version = 2 };
    std.debug.assert(&pv1 != &pv2);
    std.debug.assert(ctx2.hash(pv1) == ctx2.hash(pv2));
    std.debug.assert(ctx2.eql(pv1, pv2));
    std.debug.assert(ctx2.hash(pv2) != ctx2.hash(pv3));
}

This is the error encountered when I do zig run hmap_test.zig:

Output
General protection exception (no address available)
/home/me/zig_stuff/zig-linux-x86_64-0.14.0-dev/lib/std/mem.zig:1747:5: 0x1080422 in update (hmap_test)
    const value: T = @bitCast(buffer.*);
    ^
/home/me/zig_stuff/var_doctor/hmap_test.zig:51:22: 0x1072964 in hash (hmap_test)
        wyhash.update(s.package.author);
                     ^
/home/me/zig_stuff/zig-linux-x86_64-0.14.0-dev/lib/std/hash_map.zig:1100:34: 0x10c4866 in putAssumeCapacityNoClobberContext (hmap_test)
            const hash = ctx.hash(key);
                                 ^
/home/me/zig_stuff/zig-linux-x86_64-0.14.0-dev/lib/std/hash_map.zig:1689:58: 0x10a7548 in grow (hmap_test)
                    map.putAssumeCapacityNoClobberContext(k, v, ctx);
                                                         ^
/home/me/zig_stuff/zig-linux-x86_64-0.14.0-dev/lib/std/hash_map.zig:1536:30: 0x1090bb7 in growIfNeeded (hmap_test)
                try self.grow(allocator, capacityForSize(self.load() + new_count), ctx);
                             ^
/home/me/zig_stuff/zig-linux-x86_64-0.14.0-dev/lib/std/hash_map.zig:1357:34: 0x1072b39 in getOrPutContextAdapted__anon_9187 (hmap_test)
                self.growIfNeeded(allocator, 1, ctx) catch |err| {
                                 ^
/home/me/zig_stuff/zig-linux-x86_64-0.14.0-dev/lib/std/hash_map.zig:1342:56: 0x105be8e in getOrPutContext (hmap_test)
            const gop = try self.getOrPutContextAdapted(allocator, key, ctx, ctx);
                                                       ^
/home/me/zig_stuff/zig-linux-x86_64-0.14.0-dev/lib/std/hash_map.zig:1268:52: 0x103feb0 in putContext (hmap_test)
            const result = try self.getOrPutContext(allocator, key, ctx);
                                                   ^
/home/me/zig_stuff/zig-linux-x86_64-0.14.0-dev/lib/std/hash_map.zig:552:45: 0x103a639 in put (hmap_test)
            return self.unmanaged.putContext(self.allocator, key, value, self.ctx);
                                            ^
/home/me/zig_stuff/var_doctor/hmap_test.zig:94:21: 0x103a4b4 in registerPackageVersioned (hmap_test)
        try map2.put(s2, std.ArrayList([]const u8).init(map2.allocator));
                    ^
/home/me/zig_stuff/var_doctor/hmap_test.zig:117:33: 0x103abe3 in main (hmap_test)
    try registerPackageVersioned(&map1, &map2, "James", "West Derrickborough", 6, "James.West Derrickborough.6.ext");
                                ^
/home/me/zig_stuff/zig-linux-x86_64-0.14.0-dev/lib/std/start.zig:617:37: 0x1039ad2 in posixCallMainAndExit (hmap_test)
            const result = root.main() catch |err| {
                                    ^
/home/me/zig_stuff/zig-linux-x86_64-0.14.0-dev/lib/std/start.zig:248:5: 0x10396af in _start (hmap_test)
    asm volatile (switch (native_arch) {
    ^
???:?:?: 0x0 in ??? (???)
zsh: IOT instruction  zig run hmap_test.zig

Any input is appreciated. Thank you.

You must allocate the keys, using an allocator, for both maps.


In registerPackage the const s allocates the memory for the Package in the stack. The stack is reset and Package lifetime ends when the function exits.
The same holds for const s2 in function registerPackageVersioned.

What is happening is:

  • try map2.put(s2, ... is called
  • s2 is const s2 = PackageVersioned{ .package = s1_ptr, .version = version }; which is valid because we are in the same function.
  • s1_ptr is invalid because s1_ptr is a pointer to s1 allocated in const s1_ptr = try registerPackage(map1, author, name); and replaced on the stack by other contents.
  • in map2.put PVCtx hash is called and in the line: wyhash.update(s.package.author); the program crashes because s.package is invalid.

You can use const p = allocator.create(Package); (p type is *Package)
and you must free the memory when done, using allocator.destroy(p);

See: Zig's HashMap - Part 1 it is a tutorial for HashMap, it starts with nodes list allocations using allocator create and destroy.

std.HashMap calls grow method to reallocate the memory automatically.
Already allocated memory is deallocated.

You use map1 key as the key of map2 instance.
But after the map1 is reallocated, the key of map2 is invalidated.

So as @dimdin mentioned, Package data must be allocated on the heap.

I understand what is happening now but I am having trouble articulating a solution. In the linked blogpost, I can wrap my head around how they use var node = allocator.create(...). Since it returns a pointer to a Node, the assignment is straightforward like LL.head = node.

In my case I am not sure how to use the product of allocator.create() correctly. For example, here is the updated code:

hmap_test.zig
const std = @import("std");

//
// Package struct stuff BEGIN
//
const Package = struct {
    author: []const u8,
    name: []const u8,
};

const PCtx = struct {
    const Self = @This();
    pub fn eql(_: Self, s1: Package, s2: Package) bool {
        return std.mem.eql(u8, s1.author, s2.author) and std.mem.eql(u8, s1.name, s2.name);
    }
    pub fn hash(_: Self, s: Package) u64 {
        var wyhash = std.hash.Wyhash.init(0);
        wyhash.update(s.author);
        wyhash.update(s.name);
        return wyhash.final();
    }
};

const PackageMap = std.HashMap(
    Package,
    void,
    PCtx,
    std.hash_map.default_max_load_percentage,
);
//
// Package struct stuff END
//

//
// PackageVersioned struct stuff BEGIN
//
const PackageVersioned = struct {
    package: *const Package, // This is now a pointer to TestStruct1
    version: u8,
};

const PVCtx = struct {
    const Self = @This();
    pub fn eql(_: Self, s1: PackageVersioned, s2: PackageVersioned) bool {
        return (std.mem.eql(u8, s1.package.author, s2.package.author) and
            std.mem.eql(u8, s1.package.name, s2.package.name) and
            s1.version == s2.version);
    }
    pub fn hash(_: Self, s: PackageVersioned) u64 {
        var wyhash = std.hash.Wyhash.init(0);
        wyhash.update(s.package.author);
        wyhash.update(s.package.name);
        wyhash.update(std.mem.asBytes(&s.version));
        return wyhash.final();
    }
};

const PackageVersionedMap = std.HashMap(
    PackageVersioned,
    std.ArrayList([]const u8),
    PVCtx,
    std.hash_map.default_max_load_percentage,
);
//
// PackageVersioned struct stuff END
//

/// This function checks if the author, name combination exists in map1. If not it is
/// `put`. Then the pointer to the Package key having the corresponding author and name
/// is returned.
fn registerPackage(
    p_map: *PackageMap,
    author: []const u8,
    name: []const u8,
    allocator: std.mem.Allocator,
) !*Package {
    if (!p_map.contains(Package{ .author = author, .name = name })) {
        const pkg = try allocator.create(Package);
        pkg.author = try allocator.dupe(u8, author);
        pkg.name = try allocator.dupe(u8, name);
        try p_map.put(pkg.*, {});
    }
    return p_map.getKeyPtr(Package{ .author = author, .name = name }).?;
}

/// This function first gets a `*Package `using `registerPackage`. Then it checks if
/// the combination of `*Package` and `version` exists in map2. If not, it is `put`
/// as the key with the value being an empty ArrayList. Then the path is appended
/// to this ArrayList.
fn registerPackageVersioned(
    p_map: *PackageMap,
    pv_map: *PackageVersionedMap,
    author: []const u8,
    name: []const u8,
    version: u8,
    value: []const u8,
    allocator: std.mem.Allocator,
) !void {
    const pkg_ptr = try registerPackage(p_map, author, name, allocator);
    const pkg_versioned = PackageVersioned{ .package = pkg_ptr, .version = version };
    if (!pv_map.contains(pkg_versioned)) {
        const pv = try allocator.create(PackageVersioned);
        pv.package = pkg_ptr;
        pv.version = version;
        try pv_map.put(
            pv.*,
            std.ArrayList([]const u8).init(allocator),
        );
    }
    var list = pv_map.getPtr(pkg_versioned).?;
    try list.append(value);
    std.debug.print("REGISTERED: {s}.{s}.{}\n", .{ author, name, version });
}

pub fn main() !void {
    var arena = std.heap.ArenaAllocator.init(std.heap.page_allocator);
    defer arena.deinit();
    const allocator = arena.allocator();

    var p_map = PackageMap.init(allocator);
    defer p_map.deinit();
    var pv_map = PackageVersionedMap.init(allocator);
    defer pv_map.deinit();

    // Just register a bunch of packages. No dupes as of now.
    try registerPackageVersioned(&p_map, &pv_map, "Sharon", "Penamouth", 14, "Sharon.Penamouth.14.ext", allocator);
    try registerPackageVersioned(&p_map, &pv_map, "Travis", "Jerryburgh", 1, "Travis.Jerryburgh.1.ext", allocator);
    try registerPackageVersioned(&p_map, &pv_map, "Nicole", "Newmanmouth", 3, "Nicole.Newmanmouth.3.ext", allocator);
    try registerPackageVersioned(&p_map, &pv_map, "Brittany", "West Karenfurt", 10, "Brittany.West Karenfurt.10.ext", allocator);
    try registerPackageVersioned(&p_map, &pv_map, "Karen", "Bushview", 20, "Karen.Bushview.20.ext", allocator);
    try registerPackageVersioned(&p_map, &pv_map, "Adam", "South Michael", 10, "Adam.South Michael.10.ext", allocator);
    try registerPackageVersioned(&p_map, &pv_map, "James", "West Derrickborough", 6, "James.West Derrickborough.6.ext", allocator);
    try registerPackageVersioned(&p_map, &pv_map, "William", "Reynoldsville", 13, "William.Reynoldsville.13.ext", allocator);
    try registerPackageVersioned(&p_map, &pv_map, "Andrew", "East Alyssaton", 10, "Andrew.East Alyssaton.10.ext", allocator);
    try registerPackageVersioned(&p_map, &pv_map, "Ashley", "Port Hailey", 12, "Ashley.Port Hailey.12.ext", allocator);
    try registerPackageVersioned(&p_map, &pv_map, "Jessica", "North Mary", 7, "Jessica.North Mary.7.ext", allocator);
    try registerPackageVersioned(&p_map, &pv_map, "Lori", "Johnsport", 1, "Lori.Johnsport.1.ext", allocator);
    try registerPackageVersioned(&p_map, &pv_map, "Cody", "Mcdanielstad", 19, "Cody.Mcdanielstad.19.ext", allocator);
    try registerPackageVersioned(&p_map, &pv_map, "Megan", "Lake Kenneth", 15, "Megan.Lake Kenneth.15.ext", allocator);
    try registerPackageVersioned(&p_map, &pv_map, "Steven", "Port Stacyville", 8, "Steven.Port Stacyville.8.ext", allocator);
    try registerPackageVersioned(&p_map, &pv_map, "Bradley", "New Patricia", 7, "Bradley.New Patricia.7.ext", allocator);
    try registerPackageVersioned(&p_map, &pv_map, "Lance", "West Craigfort", 3, "Lance.West Craigfort.3.ext", allocator);
    try registerPackageVersioned(&p_map, &pv_map, "Courtney", "Lake Alexandrashire", 8, "Courtney.Lake Alexandrashire.8.ext", allocator);
    try registerPackageVersioned(&p_map, &pv_map, "Julie", "Danielport", 2, "Julie.Danielport.2.ext", allocator);
    try registerPackageVersioned(&p_map, &pv_map, "Christopher", "Jimenezshire", 17, "Christopher.Jimenezshire.17.ext", allocator);
    try registerPackageVersioned(&p_map, &pv_map, "Anthony", "West Andrea", 20, "Anthony.West Andrea.20.ext", allocator);

    // Try only registering packages
    // _ = try registerPackage(&map1, "Sharon", "Penamouth", allocator);
    // _ = try registerPackage(&map1, "Travis", "Jerryburgh", allocator);
    // _ = try registerPackage(&map1, "Nicole", "Newmanmouth", allocator);
    // _ = try registerPackage(&map1, "Brittany", "West Karenfurt", allocator);
    // _ = try registerPackage(&map1, "Karen", "Bushview", allocator);
    // _ = try registerPackage(&map1, "Adam", "South Michael", allocator);
    // _ = try registerPackage(&map1, "James", "West Derrickborough", allocator);
    // _ = try registerPackage(&map1, "William", "Reynoldsville", allocator);
    // _ = try registerPackage(&map1, "Andrew", "East Alyssaton", allocator);
    // _ = try registerPackage(&map1, "Ashley", "Port Hailey", allocator);
    // _ = try registerPackage(&map1, "Jessica", "North Mary", allocator);
    // _ = try registerPackage(&map1, "Lori", "Johnsport", allocator);
    // _ = try registerPackage(&map1, "Cody", "Mcdanielstad", allocator);
    // _ = try registerPackage(&map1, "Megan", "Lake Kenneth", allocator);
    // _ = try registerPackage(&map1, "Steven", "Port Stacyville", allocator);
    // _ = try registerPackage(&map1, "Bradley", "New Patricia", allocator);
    // _ = try registerPackage(&map1, "Lance", "West Craigfort", allocator);
    // _ = try registerPackage(&map1, "Courtney", "Lake Alexandrashire", allocator);
    // _ = try registerPackage(&map1, "Julie", "Danielport", allocator);
    // _ = try registerPackage(&map1, "Christopher", "Jimenezshire", allocator);
    // _ = try registerPackage(&map1, "Anthony", "West Andrea", allocator);

    // Human readable representation of PackageMap
    var p_iter = p_map.keyIterator();
    while (p_iter.next()) |key| {
        std.debug.print("{s}.{s}\n", .{ key.author, key.name });
    }

    // Print a human readable representation of our package database
    var pv_iter = pv_map.iterator();
    while (pv_iter.next()) |entry| {
        std.debug.print(
            "[{s}, {s}, {}]: [ ",
            .{
                entry.key_ptr.package.author,
                entry.key_ptr.package.name,
                entry.key_ptr.version,
            },
        );
        for (entry.value_ptr.items) |item| {
            std.debug.print("{s}, ", .{item});
        }
        std.debug.print("]\n", .{});
    }
}

test "hash functions" {
    const allocator = std.testing.allocator;
    const author1 = "author1";
    const author1_dupe1 = try allocator.dupe(u8, author1);
    defer allocator.free(author1_dupe1);
    const author1_dupe2 = try allocator.dupe(u8, author1);
    defer allocator.free(author1_dupe2);
    std.debug.assert(&author1_dupe1 != &author1_dupe2);

    const ctx1 = PCtx{};
    const p1 = Package{ .author = author1_dupe1, .name = "name1" };
    const p2 = Package{ .author = author1_dupe2, .name = "name1" };
    const p3 = Package{ .author = "author2", .name = "name2" };
    std.debug.assert(&p1 != &p2);
    std.debug.assert(ctx1.hash(p1) == ctx1.hash(p2));
    std.debug.assert(ctx1.eql(p1, p2));
    std.debug.assert(ctx1.hash(p1) != ctx1.hash(p3));

    const ctx2 = PVCtx{};
    const pv1 = PackageVersioned{ .package = &p1, .version = 1 };
    const pv2 = PackageVersioned{ .package = &p2, .version = 1 };
    const pv3 = PackageVersioned{ .package = &p2, .version = 2 };
    std.debug.assert(&pv1 != &pv2);
    std.debug.assert(ctx2.hash(pv1) == ctx2.hash(pv2));
    std.debug.assert(ctx2.eql(pv1, pv2));
    std.debug.assert(ctx2.hash(pv2) != ctx2.hash(pv3));
}

I am createing in registerPackage and registerPackageVersioned. But then I am immediately derefencing the product of create which makes me think I am using it wrong. The same error persists.

Any hints about this? Sorry if I did not make sense.

Or you can use ArrayHashMap and use indexes.

1 Like

This is because adding a key to the HaskMap may lead to reallocate entries.
So you can not use getKeyPtr method to return the key.

You need to declare PackageMap key type as the pointer.

const PackageMap = std.HashMap(
    *Package, // MODIFIED
    void,
    PCtx,
    std.hash_map.default_max_load_percentage,
);

And PCtx also accepts the pointer.

const PCtx = struct {
    const Self = @This();
    pub fn eql(_: Self, s1: *Package, s2: *Package) bool { // MODIFIED
        return std.mem.eql(u8, s1.author, s2.author) and std.mem.eql(u8, s1.name, s2.name);
    }
    pub fn hash(_: Self, s: *Package) u64 { // MODIFIED
        var wyhash = std.hash.Wyhash.init(0);
        wyhash.update(s.author);
        wyhash.update(s.name);
        return wyhash.final();
    }
};

registerPackage function is following:

fn registerPackage(
    p_map: *PackageMap,
    author: []const u8,
    name: []const u8,
    allocator: std.mem.Allocator,
) !*Package {
    const pkg = try allocator.create(Package);
    pkg.* = .{
        .author = try allocator.dupe(u8, author),
        .name = try allocator.dupe(u8, name),
    };

    const entry = try p_map.getOrPut(pkg);
    if (entry.found_existing) {
        allocator.free(pkg.author);
        allocator.free(pkg.name);
        allocator.destroy(pkg);
        return entry.key_ptr.*;
    }

    entry.value_ptr.* = {};
    return pkg;
}

Additionally, in Human readable representation of PackageMap line, I’ve modifed following:

    var p_iter = p_map.keyIterator();
    while (p_iter.next()) |key| {
       // std.debug.print("{s}.{s}\n", .{ key.author, key.name });
       std.debug.print("{s}.{s}\n", .{ key.*.author, key.*.name });
    }

Thanks a lot. I have some thinking about to do now because without your help here it would have never occurred to me that the hashmap having a pointer type key was something that was possible in the first place.