Weird behavior of "closure" [Solved, stack lifetime issue]

I implement this closure, taking https://github.com/ziglang/zig/issues/1048#issuecomment-1793842005 as reference. However, I find a weird thing. This is an example code (you can run it on zigbin):

const print = @import("std").debug.print;
const MAX_TEXT_LEN = 255;
const Vector2 = @Vector(2, f16);

const Screen = struct {
    const Self = @This();
    a: Vector2,
    input_text_field: InputTextField,

    pub fn new() Self {
        var screen: Self = .{ .a = .{1, 2}, .input_text_field = undefined };
        print("BEGINNIN: {any}\n", .{screen});
        screen.input_text_field = InputTextField.new(
            10,
            undefined
        );
        screen.input_text_field.enter_key_press_func = screen.enter_key_press_func();
        return screen;
    }

    pub inline fn enter_key_press_func(self: *Self) *const fn () void {
        return (struct {
            var screen: *Self = undefined;
            
            pub fn init(itf: *Self) *const @TypeOf(run) {
                screen = itf;
                print("INIT: {any}\n", .{screen});
                return &run;
            }

            fn run() void {
                print("ENTER: {any}\n", .{screen});
            }
        }).init(self);
    }
};

const InputTextField = struct {
    const Self = @This();
    text: [MAX_TEXT_LEN: '\x00']u8 = [_: '\x00']u8{'\x00'} ** MAX_TEXT_LEN,
    a: u8,
    enter_key_press_func: *const fn () void,

    pub fn new(a: u8, enter_key_press_func: *const fn () void) Self {
        var res: Self = .{
            .a = a,
            .enter_key_press_func = enter_key_press_func
        };
        res.a = a;
        return res;
    }

    pub fn handleKeyPress(self: *Self) void {
        self.enter_key_press_func();
    }
};

pub fn main() void {
    var screen = Screen.new();
    screen.input_text_field.handleKeyPress();
    print("AFTER: {any}\n", .{screen});
}

The output is:

zig build run
BEGINNIN: main.Screen{ .a = { 1.0e+00, 2.0e+00 }, .input_text_field = main.InputTextField{ .text = { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, .a = 0, .enter_key_press_func = fn () void@0 } }
INIT: main.Screen{ .a = { 1.0e+00, 2.0e+00 }, .input_text_field = main.InputTextField{ .text = { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, .a = 10, .enter_key_press_func = fn () void@1 } }
ENTER: main.Screen{ .a = { -1.472e+04, 1.609325408935547e-05 }, .input_text_field = main.InputTextField{ .text = { 0, 0, 0, 0, 0, 0, 0, 0, 48, 243, 14, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 0, 0, 0, 0, 0, 0, 0, 0, 204, 133, 13, 112, 225, 0, 0, 0, 0, 0, 0, 254, 127, 0, 0, 3, 0, 0, 0, 0, 0, 0, 0, 148, 154, 71, 196, 254, 127, 0, 0, 64, 186, 0, 1, 0, 0, 0, 0, 160, 151, 71, 196, 254, 127, 0, 0, 112, 154, 71, 196, 254, 127, 0, 0, 200, 152, 71, 196, 254, 127, 0, 0, 3, 0, 0, 0, 0, 0, 0, 0, 48, 243, 14, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 186, 207, 14, 1, 0, 0, 0, 0, 6, 0, 0, 0, 0, 0, 0, 0, 186, 207, 14, 1, 0, 0, 0, 0, 6, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 2, 0, 0, 0, 0, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 0, 0, 0, 0, 0, 204, 133, 13, 112, 225, 178, 86, 224, 152, 71, 196, 254, 127, 0, 0, 187, 26, 9, 1, 0, 0, 0, 0, 112, 154, 71, 196, 254, 127, 0, 0, 117, 212, 14, 1, 0, 0, 0, 0, 160, 151, 71, 196, 254, 127, 0 }, .a = 3, .enter_key_press_func = fn () void@2 } }
AFTER: main.Screen{ .a = { 1.0e+00, 2.0e+00 }, .input_text_field = main.InputTextField{ .text = { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, .a = 10, .enter_key_press_func = fn () void@10688d0 } }

You can see that ENTER log shows different content. I don’t know why.

This is stack lifetime problem. Essentially you are returning a pointer to a local variable.

pub fn new() Self {
    var screen: Self = ...; // A variable on the stack. All pointers to it become invalid after leaving the function
    ...
    // A pointer to `screen`is given to the function, which stores it in a global variable:
    screen.input_text_field.enter_key_press_func = screen.enter_key_press_func();
    return screen; // The function is done, screen is copied and returned to the caller.
    // This makes the pointer in that global variable invalid.
}
4 Likes

Get it. Thank you so much!