Just quickly, you don’t want to call create – instead, call the .init method of StringHashMap, supplying your allocator as an argument. Then when you deinit your own struct, call the .deinit method of StringHashMap.
Hopefully that will get you going, sorry for the short reply.
The error here is this: mem.create creates a pointer *StringArrayHashMap(...), but you don’t store a pointer in your struct, as you put by_name: std.StringArrayHashMap(MyOtherThing), without a *.
Generally it doesn’t make sense to store them as a pointer anyways, unless you want to share it with other structs.
The second problem is things.by_name.init(mem);. The init() function is not a member function, so it needs to be called like this .by_name = std.StringArrayHashMap(MyOtherThing).init(mem)
Generally most data structures in the standard library follow this simple pattern:
To init: ...val = std.DataStructure(T).init(allocator)
To deinit: ...val.deinit()
Additionally I would advice against the C-style functions in favor of using member functions, which makes it easier to read/write (e.g. instead of deinit_MyThing(myThing), it would be myThing.deinit()) and it conforms to convention used by the standard library.
Here is your example fixed and rewritten in that style:
pub const MyThing = struct {
name: []const u8,
by_name: std.StringArrayHashMap,
pub fn init(mem: Allocator) !MyThing {
var thing: MyThing = . {
.name = "boo-yeah",
.by_name = std.StringHashMap(MyOtherThing).init(mem),
}
thing.put("cool-thing", MyOtherThing.init(...));
return thing;
}
pub fn deinit(self: MyThing) void {
var it = self.by_name.iterator();
while(it.next()) |entry| {
entry.value_ptr.deinit(); // Don't forget to deinit the entries of the hash map as well. This is not done automatically.
}
self.by_name.deinit();
}
pub fn put(self: *MyThing, thing_name: []const u8, awesome_thing: MyOtherThing) !void {
try self.by_name.put(thing_name, awesome_thing);
}
};
Generally most data structures in the standard library follow this simple pattern:
To init: ...val = std.DataStructure(T).init(allocator)
To deinit: ...val.deinit()
Wow, lol I had totally missed it. Aha, ok, I really appreciate this knowledge. Very helpful. Thank you so much for taking the time with an excellent example! I really appreciate your help.
Additionally I would advise against the C-style functions in favor of using member functions,
lol well spotted! Can do.
I was wondering…only from a place of curiosity / learning, no challenge… for you, what are the benefits of a member fn approach from your pov?
I would say it is almost entirely style / code-organization.
I think it helps people by:
having functions associated with a scope / type, that those functions are related to (grouping things explicitly instead of via naming convention)
it is convention/how most Zig code is written
method syntax can be more convenient because of scoped (and thus shorter) names
it helps code editors, because when you type my_instance. your editor can lookup what methods exist and suggest those to you (in theory that could be done for freestanding functions), . is a useful trigger character for a code editor to start trying to provide autocomplete/suggestions, because at that point the instance is already known so the possible completions are already a useful more narrow subset of everything that could be called/used
While the compiler probably wouldn’t care that much either way.