Confusion with if and optionals

I have this code:

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.

What is the solution to this?

1 Like

(post deleted by author)

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.

I was about to suggest that as well, but OP said

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 :slight_smile:

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

Thanks, I had done the assigning to a variable first, but that felt like I was doing extra stuff for nothing so I was looking for a better solution.

(post deleted by author)

I’m still confused about how const works in Zig, how it propagates, and how it affects a type. But I wrote this little test program.

const std = @import("std");

const Foo = struct {
    pub fn deinit(self: *Foo) void {
        _ = self;
    }
};

pub fn main() !void {
    var opt: ?Foo = .{};
    opt = .{}; // here to force var

    // Works.
    opt.?.deinit();

    // Works.
    if (opt) |*f| {
        f.deinit();
    }

    // Doesn't work.
    // if (opt) |f| {
    //     f.deinit();
    // }
    // src/main.zig:17:10: error: expected type '*main.Foo', found '*const main.Foo'
    // src/main.zig:17:10: note: cast discards const qualifier
    // src/main.zig:4:25: note: parameter type declared here

    // ---

    const opt2: ?Foo = .{};

    // Doesn't work.
    // opt2.?.deinit();
    // src/main.zig:30:11: error: expected type '*main.Foo', found '*const main.Foo'
    // src/main.zig:30:11: note: cast discards const qualifier
    // src/main.zig:4:25: note: parameter type declared here

    // Doesn't work.
    // if (opt2) |*f| {
    //     f.deinit();
    // }

    // Doesn't work.
    // if (opt2) |f| {
    //     f.deinit();
    // }
    // src/main.zig:45:10: error: expected type '*main.Foo', found '*const main.Foo'
    // src/main.zig:45:10: note: cast discards const qualifier
    // src/main.zig:4:25: note: parameter type declared here
}

So it seems like the |*val| syntax does sometimes work? It depends if the value was originally declared with var or const?

But KV is desribed as “A copy of a key and value which are no longer in the map” so a pointer wouldn’t make sense

Ah sorry, I was not aware you where talking about a map in std.

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

I guess this is the solution code:

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);
}

does this work?

const gop = try entry.fields.getOrPut(config.field_name);
if (gop.found_existing) gop.value_ptr.deinit(allocator);
gop.value_ptr.* = new_value;
1 Like