I am making a library, when I say user I mean whoever is using this library, not me using the std library.
I have this struct:
const Foo = struct {
bars: std.StringHashMap(Bar),
};
The Bar struct has a deinit function that has an Allocator as a parameter.
Most of the ways to interact with this struct is just updating bars in with the hashmap functions.
StringHashMap does not own the memory of its keys and values.
I could do something similar to this and make the user responsible for deallocating the memory with Bar.deinit or, because the type is always the same, should I provide a Foo.deinit function that loops through each bar and runs Bar.deinit.
Then who owns the memory? If the user puts a value into the StringHashMap, it documentation says that they are responsible for freeing the memory, but that is done by Foo.deinit then, not the user.
Should I make put functions for Foo that just call the corresponding StringHashMap function, that would make ownership clearer, but would add a lot of bloat.
Does anyone know any std libary structs that might be similar to this?
For HashMaps, the map does NOT own the memory. The user is incharge of managing the memory for any heap allocated structs. Calling deinit on a Map only clears the memory used by the Map to store keys and values.
For a StringHashMap, the string keys would be managed by the caller. It is up to you as the user to manage those.
That being said, Bar here would be part of that memory. If Bar has any members that are heap allocated, you would need to handle that yourself. When you are cleaning up, you would probably want to iterate over the keys and values and clean up both.
var iter = bars.iterator()
while (iter.next()) |entry| {
// If the string keys need to be freed
gpa.free(entry.key_ptr.*);
// If Bar has a deinit function, use it
entry.value_ptr.deinit(gpa);
}
I would put this in the Foo.deinit function, but it depends on how you are design the API. If you want Foo to take ownership of all Bars placed in Foo, then I would say having the Foo.deinit call all the Bar.deinit in a loop like above would be expected.
In this case, the documentation calls out the “caller” as the one responsible for managing the memory. Foo.deinit would be a good place to handle this, so you are following the documentation.
I made this very unclear, sorry. I am making a library with the Foo struct. When I say the user, I mean the user of my library containing Foo, not me the user using the std library.
That’s partially on me as well, probably didn’t read it closely enough.
So if I understand correctly, you are asking whether you should make the user of your library should handle ownership of Bar? The following assumes Bar is a type from your library.
My personal approach would be that I would expect Foo to have a deinit function and I would expect Foo to have ownership of Bar at this point. If that is not the case, I (as a hypothetical user) would expect it documented somewhere. That is my default approach in this case.
As for the put question, I would expect that rather than having to call foo.bars.put manually if this is a more complex type. If it is a simple wrapper around the StringHashMap, that may be untenable because you would need to basicaly wrap all of the get* or put* methods. Adn at that point, there may not be value in Foo if it is a simple wrapper and instead you should let the user determine how they want to keep track of Bar.