Running defer inside init function doesn't initialize pointer fields

So i have this struct snake that i am creating and it has 3 important fields,

body: std.ArrayList(rl.Vector2),
head: *const rl.Vector2,
tail: *const rl.Vector2,

And i have the following init function:

pub fn init(game_ctx: *Game) Self {
    var this = Self.getDefault();
    this.game_ctx = game_ctx;

    this.body = std.ArrayList(rl.Vector2).init(Game.allocator);
    defer this.setTail(); // sets this.tail to a pointer to the first element in the list
    defer this.setHead(); // sets this.head to a pointer to the last element in the list

    this.addBodyPart(0, 0);
    this.addBodyPart(0, 1);
    this.addBodyPart(1, 1);

    return this;
}

when i run this i get an error somewhere down the line that the fields head and tail have both the value 0x0

when i have the init method without the defers:

pub fn init(game_ctx: *Game) Self {
    var this = Self.getDefault();
    this.game_ctx = game_ctx;

    this.body = std.ArrayList(rl.Vector2).init(Game.allocator);
    
    this.addBodyPart(0, 0);
    this.addBodyPart(0, 1);
    this.addBodyPart(1, 1);

    this.setTail(); // sets this.tail to a pointer to the first element in the list
    this.setHead(); // sets this.head to a pointer to the last element in the list

    return this;
}

It works just fine!

I am not understanding something correctly??

defer is like a stack, it runs code in the reverse order.

{
    defer std.debug.print("last\n", .{});
    defer std.debug.print("first\n", .{});
}

I think two different defer will be executed in reverse order. You’d make it more clear if you went:

defer {
    this.setTail();
    this.setHead();
}

Edit:

This is best for freeing resources:

const a = try initA();
defer a.deinit();

const b = try initB(a);
defer b.deinit();

Performing a.deinit() could invalidates b since it depends on a. Performing b.deinit(); after could go wrong. It’s best to perform b.deinit(); first and then a.deinit();.

The order doesn’t matter, the problem is that the struct field is not being set when running the function that set the field using defer.

Does addBodyPart depend on setTail or setHead being called beforehand? If not I don’t know what could be wrong.

I think the return value is already copied before the defer statements run.
You could add a scope that begins before the first defer and ends before the return so that the defer runs before the return.

But I don’t really understand why you are using defer at all.

3 Likes

Yep, it’s quite an unusual usage of defer imho.
Usually it is used to de-initialize things,
but not to perform some final steps of initialization.

2 Likes

Oh right! If this isn’t a reference to the data written by setTail and setHead, that could be it.

unusual or not, if modification of the structure’s data is not reflected in the return value, that’s wicked.

const std = @import("std");
const builtin = @import("builtin");

const T = struct {
    v: i31,
    touched: bool = false,
    pub fn finalize(self: *T) void {
        self.*.touched = true;
    }
};

fn fun(v: i31) T {
    var t: T = .{ .v = v };
    defer t.finalize();
    return t;
}

test "tst fun t" {
    const t = fun(42);
    try std.testing.expectEqual(true, t.touched);
}

gives

Test [1/1] main.test.tst fun t... expected true, found false
Test [1/1] main.test.tst fun t... FAIL (TestExpectedEqual)
2 Likes

That’s the same thing that i was thinking when making this topic , good way of visualizing the problem btw!

I did think of this afterward:

pub fn init(game_ctx: *Game) Self {
    var this = Self.getDefault();
    this.game_ctx = game_ctx;

    this.body = std.ArrayList(rl.Vector2).init(Game.allocator);
    defer Self.setHead(&this);
    defer Self.setTail(&this);

    this.addBodyPart(0, 0);
    this.addBodyPart(0, 1);
    this.addBodyPart(1, 1);

    return this;
}

But sadly no, same result. Althought zig should warn you about how I incorrectly try to use a method on a non-pointer variable.

The real visualization is printf debugging :wink: :slight_smile:

const std = @import("std");
const builtin = @import("builtin");

const T = struct {
    v: i31,
    touched: bool = false,
    pub fn finalize(self: *T) void {
        std.debug.print("addr in finalize: {x}...", .{@intFromPtr(self)});
        self.*.touched = true;
        std.debug.print("finalized {any}\n", .{self.*});
    }
};

fn fun(v: i31) T {
    var t: T = .{ .v = v };
    defer t.finalize();
    std.debug.print("t in fun addr: {x} - t = {any}\n", .{ @intFromPtr(&t), t });
    return t;
}

test "tst fun t" {
    const t = fun(42);
    std.debug.print("addr in tst: {x}...{any}", .{ @intFromPtr(&t), t });
    try std.testing.expectEqual(true, t.touched);
}

pub fn main() !void {}

which shows that the defer self indeed is a different one, IIUIC, namely the local variable in the fun fn:

Test [1/1] main.test.tst fun t... t in fun addr: 7fff2023e1d0 - t = main.T{ .v = 42, .touched = false }
addr in finalize: 7fff2023e1d0...finalized main.T{ .v = 42, .touched = true }
addr in tst: 7fff2023e220...main.T{ .v = 42, .touched = false }expected true, found false
Test [1/1] main.test.tst fun t... FAIL (TestExpectedEqual)

(excuse the intermingled stderr output mess)

If I understood result location semantics correctly, the address should’ve been the same on all occasions, shouldn’t it?

If my understanding was wrong, it is still an easy reproduction.


NOTE: I tried to verify my idea with the language docs but couldn’t find the result location rules of a return. I suppose I got that part wrong. Instructive!

1 Like

I meant that this should be/hold a reference to your data, not to use a reference to this instead. It doesn’t work because you’re still not returning &this (and you shouldn’t since it lives on the stack).