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 Package
s 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.