if (try entry.fields.fetchPut(config.field_name, new_value)) |old| {
old.value.deinit(allocator);
}
and I get this error:
src/cli/main.zig:876:22: error: expected type '*value.Value', found '*const value.Value'
old.value.deinit(allocator);
~~~~~~~~~^~~~~~~
src/cli/main.zig:876:22: note: cast discards const qualifier
src/core/value.zig:64:25: note: parameter type declared here
pub fn deinit(self: *Self, allocator: std.mem.Allocator) void {
But the return type of fetchPut is !?KV, and my hash map owns Value: std.StringHashMap(Value) so old should be of type KV and the value field of that is of type Value not *const Value right? Is it the if () || syntax, is old like a const variable, I even tried doing |*old| but that didn’t work either.
the problem is that method call syntax that you use in the deinit call can coerce const thing: T to *const T and var thing: T to *T. you should attempt capturing by pointer |*old| so that old is of type *T. if that doesn’t work, you would need to assign var copy = old and call copy.deinit(). That sounds to me like a code smell, but I don’t know what the structure of your Value; if it needs a deinit because it owns some pointers inside, then this is fine.
The issue is, that the return value of a function is a constant, and capturing a pointer to it will still get you a pointer to constant memory.
I think fetchPut returning ?!*KV instead of ?!KV should solve it.
And I think, zig likely saved you from a bug there
Edit: thinking about it again, it is probably necessary to assign the return value to a variable instead of returning a pointer, because at that address the new value is when the function returns
var optional = try entry.fields.fetchPut(config.field_name, new_value);
if (optional) |*old| {
old.value.deinit(allocator);
}
This looks like the part of you code that works, if function return types are constant, we have it assigned to the var optional anyways, the only difference I see is that we are accessing field of old where your function calls the deinit function directly on f
if (try entry.fields.fetchPut(config.field_name, new_value)) |kv| {
// Assign because `old_value` is const
var old_value = kv.value;
old_value.deinit(allocator);
}
Aarg. I’ve seen that syntax once when I was using some random library… I guess I understand it… but I don’t like it.
So the old_value is const, but it stores a pointer. So you copy the pointer value to another variable that isn’t const and now you can modify the thing the pointer points to?
which tracks “use after deinit” in debug mode. Copying kv.value has incorrect semantics in this case because old_value (the copy) gets set to undefined instead of wherever the original Value is. Using fetchPut also has incorrect semantics
If any container, whether const or var, contains a non-const pointer (e.g. *u32) you are allowed to mutate the pointer contents. If a container is var then you are allowed to mutate the pointer itself e.g.
var x: u32 = 0;
var y: u32 = 2;
const foo = .{.x = &x};
var bar = .{.x = &x};
foo.x.* = 1; // fine
foo.x = &y; // not fine
bar.x.* = 2; // fine
bar.x = &y; // fine
Same goes for variables
var x: u32 = 0;
var y: u32 = 0;
const z: *u32 = &x;
var w: *u32 = &x;
z.* = 1; // fine
z = &y; // not fine
w.* = 1; // fine
w = &y; // fine