Please, I need some sanity checks here. This Zig behaviour surprised me, after using zig for a couple of years. I tried to distil it into a simple test:
const std = @import("std");
const testing = std.testing;
const Foo = struct {
id: usize,
value: usize,
other: ?*Foo,
pub fn init(id: usize) Foo {
const self = Foo{
.id = id,
.value = 0,
.other = null,
};
std.debug.print("CREATE Foo id {} (other: {*})\n", .{ self.id, self.other });
return self;
}
pub fn deinit(self: *Foo) void {
std.debug.print("DESTROY Foo id {} (other: {*}), value={}\n", .{ self.id, self.other, self.value });
}
pub fn show(self: Foo) void {
std.debug.print("Foo id {} (other: {*}), value={}\n", .{ self.id, self.other, self.value });
}
pub fn setValue(self: *Foo, value: usize) void {
self.value = value;
}
pub fn setOther(self: *Foo, other: *Foo) void {
self.other = other;
}
};
const Bar = struct {
const SIZE = 3;
id: usize,
foos: [SIZE]Foo,
pub fn init(id: usize) Bar {
var self = Bar{ .id = id, .foos = undefined };
for (&self.foos, 0..) |_, p| {
self.foos[p] = Foo.init(p);
}
for (&self.foos, 0..) |_, p| {
const o = (p + 1) % SIZE;
std.debug.print("OTHER for {} at {*} is {} at {*}\n", .{ p, &self.foos[p], o, &self.foos[o] });
self.foos[p].setOther(&self.foos[o]);
}
return self;
}
pub fn deinit(self: *Bar) void {
for (&self.foos, 0..) |_, p| {
self.foos[p].deinit();
}
}
pub fn show(self: Bar) void {
std.debug.print("Bar id {}\n", .{self.id});
for (&self.foos, 0..) |_, p| {
std.debug.print(" {} {*}: ", .{ p, &self.foos[p] });
self.foos[p].show();
}
}
pub fn populate(self: *Bar) void {
for (&self.foos, 0..) |_, p| {
self.foos[p].setValue(p * 10);
}
}
pub fn cycle(self: *Bar) void {
for (&self.foos, 0..) |_, p| {
self.foos[p].other.?.*.setValue(p * 100);
}
}
};
test "Foo" {
std.debug.print("\n", .{});
const id = 11;
var foo = Foo.init(id);
defer foo.deinit();
try testing.expectEqual(foo.id, id);
foo.show();
}
test "Bar" {
std.debug.print("\n", .{});
const id = 11;
var bar = Bar.init(id);
defer bar.deinit();
try testing.expectEqual(bar.id, id);
bar.show();
bar.populate();
bar.show();
bar.cycle();
bar.show();
}
This is the output I get:
$ zig test memory.zig
Test [1/2] test.Foo...
CREATE Foo id 11 (other: *memory.Foo@0)
Foo id 11 (other: *memory.Foo@0), value=0
DESTROY Foo id 11 (other: *memory.Foo@0), value=0
Test [2/2] test.Bar...
CREATE Foo id 0 (other: *memory.Foo@0)
CREATE Foo id 1 (other: *memory.Foo@0)
CREATE Foo id 2 (other: *memory.Foo@0)
OTHER for 0 at memory.Foo@16ba65fd8 is 1 at memory.Foo@16ba65ff0
OTHER for 1 at memory.Foo@16ba65ff0 is 2 at memory.Foo@16ba66008
OTHER for 2 at memory.Foo@16ba66008 is 0 at memory.Foo@16ba65fd8
Bar id 11
0 memory.Foo@16ba66010: Foo id 0 (other: *memory.Foo@16ba65ff0), value=0
1 memory.Foo@16ba66028: Foo id 1 (other: *memory.Foo@16ba66008), value=0
2 memory.Foo@16ba66040: Foo id 2 (other: *memory.Foo@16ba65fd8), value=0
Bar id 11
0 memory.Foo@16ba66010: Foo id 0 (other: *memory.Foo@16ba65ff0), value=0
1 memory.Foo@16ba66028: Foo id 1 (other: *memory.Foo@16ba66008), value=10
2 memory.Foo@16ba66040: Foo id 2 (other: *memory.Foo@16ba65fd8), value=20
Bar id 11
0 memory.Foo@16ba66010: Foo id 0 (other: *memory.Foo@16ba65ff0), value=0
1 memory.Foo@16ba66028: Foo id 1 (other: *memory.Foo@16ba66008), value=10
2 memory.Foo@16ba66040: Foo id 2 (other: *memory.Foo@16ba65fd8), value=20
DESTROY Foo id 0 (other: *memory.Foo@16ba65ff0), value=0
DESTROY Foo id 1 (other: *memory.Foo@16ba66008), value=10
DESTROY Foo id 2 (other: *memory.Foo@16ba65fd8), value=20
All 2 tests passed.
These are the things that surprised me:
- The addresses for
other
donāt seem to have a stable value. - The changes made through
other
when callingbar.cycle()
donāt seem to have any effect, or happened in a place I am not looking atā¦
This all seems quite basic, but I will confess I am flabbergastedā¦ Please help restore my sanity.