Thatâs a good point, though I guess the balance is that caller then needs to hold or have acess to an allocator.
Thinking about it a bit more, I think there is another nuance here that could have caught me out. Long story short, within the loop I call a method on the item in the list.
list.items[i].doX()
This method can increment a field in the item, and can also add an item to the list.
I am guessing that as long I update the item before adding an item, that the update would work.
If I add to the array, then update the item, there is a possiblity that my item wouldnât be preserved? In my mind I see the item that is executing the code being in âdead memoryâ as soon as the ArrayList that it is in is resized.
I wrote a minimal test case
const std = @import("std");
const Item = struct {
count: u32 = 0,
inv: *Inventory,
fn countThenAdd(self: *Item) !void {
self.count += 1;
try self.inv.items.append(Item{
.inv = self.inv,
});
}
fn addThenCount(self: *Item) !void {
try self.inv.items.append(Item{
.inv = self.inv,
});
self.count += 1;
}
};
const Inventory = struct {
const ItemArray = std.ArrayList(Item);
items: ItemArray,
};
test "count then add" {
var gpa = std.heap.GeneralPurposeAllocator(.{}){};
const allocator = gpa.allocator();
var inv = Inventory{
.items = Inventory.ItemArray.init(allocator),
};
try inv.items.append(Item{
.inv = &inv,
});
var i: usize = inv.items.items.len;
while (i > 0) {
i -= 1;
try inv.items.items[i].countThenAdd();
}
try std.testing.expectEqual(2, inv.items.items.len);
try std.testing.expectEqual(1, inv.items.items[0].count);
}
test "add, then count" {
var gpa = std.heap.GeneralPurposeAllocator(.{}){};
const allocator = gpa.allocator();
var inv = Inventory{
.items = Inventory.ItemArray.init(allocator),
};
try inv.items.append(Item{
.inv = &inv,
});
var i: usize = inv.items.items.len;
while (i > 0) {
i -= 1;
try inv.items.items[i].addThenCount();
}
try std.testing.expectEqual(2, inv.items.items.len);
try std.testing.expectEqual(1, inv.items.items[0].count);
}
When I tried this out, both test cases passed - but looking into the code for append, ensureTotalCapacityPrecise
is called, which calls remap
on the allocator.
remap
will not always re-allocate memory, and I feel that the case of adding then updating will not retain the new value if memory is remapped.
Is my assessment correct? Java, go etc. would circumvent this since itâs always pointers that are stored. I could also work around this by using pointers - right?
Is it just a case of being a little more careful when using structs directly in collections?
Thank you for helping me understand how these things hang together 