getPtr() delivers different pointer than the one used with getOrPut() (BUG?)

Fragment:

    fn getPtr(from_address: [] const u8) !*ConfirmationMessage {
        mutex.lock();
        var gop = try map.getOrPut(from_address);
        mutex.unlock();

        // there is a CM in the map, if it has old data free this
        if (gop.found_existing) {
            if (gop.value_ptr.value) |buffer_ptr| {
                allocator.free(buffer_ptr);
                gop.value_ptr.value = null;
            }
        }
        // there is no CM in the map under this key, so create an empty new one
        else {
            gop.value_ptr = try allocator.create(ConfirmationMessage);
            gop.value_ptr.* = ConfirmationMessage.init(null);
        }

        const real_ptr = map.getPtr(from_address);
        if (real_ptr) |ptr| {
            std.debug.assert(ptr == gop.value_ptr);
            return ptr;
        }
        unreachable;
    }

The `std.debug.assert()` shows that the two pointers are NOT the same. When I set a pointer with getOrPut() directly, then getPtr() delivers a different one.

How do I get the real value pointer stored in the map? And why is a second one created and delivered?

Add a call to lockPointers.

You assigned to the pointer here, so it can’t match the one provided by the hash map. The hash map has no idea this pointer exists.
Instead, you need to initialize the object directly using the pointer provided by the hash map:

else {
-           gop.value_ptr = try allocator.create(ConfirmationMessage);
            gop.value_ptr.* = ConfirmationMessage.init(null);
        }
1 Like

ConfirmationMessage.init() does not allocate. So your suggestion is not possible.

What will this bring to the table?

This solves this problem, too.

No, you misunderstand what the the GetOrPutResult is.

The HashMap internally stores each key in a heap allocated array and each value in another heap allocated array.

So somewhere in RAM you have an array or keys and an array of values. So you have |Key|Key|Key|...|Key| and |Value|Value|Value|...|Value|Value| somewhere. That’s different from e.g. Java where the hashmap would have pointers to the Keys/Values in an array (or another datastructure).

When you call getOrPut, it returns to you the pointer to that place in the memory. It provides you with space for your value.

So gop.value_ptr points to the place in the value array.

You can achieve the same thing with an array you have yourself: var arr: [5]i32 = .{ 1, 2, 3, 4, 5 }; var ptr: *i32 = &arr[3]; As you can see, changing the value of ptr, doesn’t effect arr. It only effects ptr. And if you change ptr to something outside of arr, you can’t effect arr through ptr anymore. For example play with this code:

const std = @import("std");

pub fn main() void {
    var arr: [5]i32 = .{ 1, 2, 3, 4, 5 };
    var x: i32 = 10;

    // effectively your get operation:
    var ptr: *i32 = &arr[3];

    // you are effectively doing this in your code afterwards:
    ptr = &x;
    ptr.* = 20;

    std.debug.print("arr is: {any}\n", .{arr});
    std.debug.print("x is: {d}\n", .{x});
}

But you want to modify the value saved by the hashmap at your specific key. To be exact, you want to replace it with a new value.

You don’t need allocation for that. That would mean that you create a new space for your AGAIN. But the hashmap already did that for your.

So you don’t need to create a space for the value first. You can just assign a new value.
That’s what @LucasSantos91 suggested.

1 Like