#struggle understanding hashmap.get() logic

1 messages · Page 1 of 1 (latest)

sharp ferry
#

the location of the hashmap structure itself isn't linked to the location of the contents it points to

#

the map could be on the stack and the contents on the heap (common), or the map could be on the heap and the contents could be allocated from the stack (uncommon but possible), or both or neither

#

get simply returns a copy of the value, getPtr returns a pointer to a value, which would become invalidated if you cause the hashmap to reallocate

#

if you append to a copy of an arraylist, while the original still exists, you invalidate the original

#

I think you have a muddy idea of what "cache" means

vestal saddle
#
const std = @import("std");

test "pointer V" {
    const ally = std.testing.allocator;
    var map: std.AutoHashMapUnmanaged(u32, []u8) = .empty;
    defer {
        var it = map.iterator();
        while (it.next()) |entry|
            ally.free(entry.value_ptr.*);
        map.deinit(ally);
    }
    try map.put(ally, 1, try ally.dupe(u8, "hello"));

    const get = map.get(1).?;
    get[0] = 'H';
    try std.testing.expectEqualStrings("Hello", map.get(1).?);

    const getptr = map.getPtr(1).?;
    getptr.*[0] = 'h';
    try std.testing.expectEqualStrings("hello", map.get(1).?);
}

test "value V" {
    const ally = std.testing.allocator;
    var map: std.AutoHashMapUnmanaged(u32, [5]u8) = .empty;
    defer map.deinit(ally);
    try map.put(ally, 1, "hello".*);

    var get = map.get(1).?;
    get[0] = 'H';
    try std.testing.expectEqualStrings("hello", &map.get(1).?);

    const getptr = map.getPtr(1).?;
    getptr.*[0] = 'H';
    try std.testing.expectEqualStrings("Hello", &map.get(1).?);
}
#

in the first case there's no difference because you get a slice either way, which points to the same memory that you mutate by either method.

#

in the second case there's a difference because you get a copy with one method but get a pointer to a value held by the hashmap in the other method.

sullen sonnet
#

good example, this i can understand, the issue i have is when dynamically allocated items are the hashmap values

#

such as a arrayList

vestal saddle
#

that it's dynamically allocated is not really the issue. You could back an ArrayList with the stack using the appropriate allocator.

#

the issue is that an ArrayList contains a pointer, so whether you get a copy of the struct containing the pointer, or get a pointer to the struct containing the pointer, that inner pointer with the data you care about is the same.

#

but, an ArrayList isn't just that pointer. Changes to capacity could be lost if you make those only to a copy.

sullen sonnet
#

oh so in fact if i expand the arraylist, it does get expanded in the original location but the capacity wont get updated

#

is this right?

#

i blv its what i was missing

#

sry im bad at explaining lol

vestal saddle
#

A simple rule is that you have to know what you're dealing with to use .get(), and if you don't want to, you're safer with .getPTr

sullen sonnet
#

i think it makes sense now, ty for the explainations 🙏

sharp ferry
#

except it can go further: the original pointer itself may be invalidated, since realloc may return a different pointer if the original could not be resized

#

but you have the right concept

sullen sonnet
#

alright yea its very clear now

vestal saddle
#
const std = @import("std");

const Person = struct {
    name: []u8,
    age: u128, // optimism
};

test {
    const ally = std.testing.allocator;
    var map: std.AutoHashMapUnmanaged(u32, Person) = .empty;
    defer {
        var it = map.iterator();
        while (it.next()) |entry|
            ally.free(entry.value_ptr.name);
        map.deinit(ally);
    }
    try map.put(ally, 1, .{
        .name = try ally.dupe(u8, "Alice"),
        .age = 20,
    });

    // big hint: this can't be const
    var get = map.get(1).?;
    get.age += 1;
    get.name[0] = 'a';
    // no change:
    try std.testing.expectEqual(20, map.get(1).?.age);
    // change:
    try std.testing.expectEqualStrings("alice", map.get(1).?.name);

    const getptr = map.getPtr(1).?;
    getptr.age += 1;
    get.name[0] = 'A';
    // change:
    try std.testing.expectEqual(21, map.get(1).?.age);
    // change:
    try std.testing.expectEqualStrings("Alice", map.get(1).?.name);
}
#

name gets modified by both methods because you get a copy of the slice's pointer, which is the same.

#

ArrayList is like that. You can click through in the std documents to see exactly what fields these types have.