Comptime self pointers

I just realized that you can do this in Zig:

const A = struct {
    value: i32 = 123,

    pub fn getPrint(comptime self: *@This()) fn () void {
        const ns = struct {
            fn print() void {
                std.debug.print("value = {d}\n", .{self.value});
            }
        };
        return ns.print;
    }
};

var a: A = .{};

pub const printA = a.getPrint();

Can actually be quite useful in certain circumstances. It’s slightly unobvious that we can do this because we tend to think of pointers as runtime types.

4 Likes

all types/values can be used at comptime, the reverse is ofc not true.

But the pointer is not used at comptime, it’s used at runtime. That’s what makes this subtle. The value of the pointer is in fact not comptime known. What the compiler knows is that the dynamic linker will fix occurrences of the address through relocations where necessary prior to execution. It can therefore effectively be treated as comptime known.

If you put this into the code:

@compileLog(self);
@compileLog(@intFromPtr(self));

You would get:

@as(*test.A, &test.a)
@as(usize, [runtime value])

This is different from a pointer that actually is comptime known:

const ptr: *anyopaque = @ptrFromInt(1234);
@compileLog(ptr);
@compileLog(@intFromPtr(ptr));
@as(*anyopaque, @as(*anyopaque, @ptrFromInt(0x4d2)))
@as(usize, 1234)
4 Likes

a comptime value used at runtime is very normal, you do it all the time.

Very interesting, thanks for sharing!

Thanks for getting into the detail to show the difference. I learn something new today.

Using my function transform library you can do this:

const std = @import("std");

const fn_binding = @import("./fn-binding.zig");

const A = struct {
    value: i32 = 123,

    pub fn print(self: *const @This()) void {
        std.debug.print("value = {d}\n", .{self.value});
    }
};

var a: A = .{};

pub const printA = fn_binding.define(A.print, .{&a});

pub fn main() void {
    printA();
    a.value = 456;
    printA();
}
value = 123
value = 456

That only works though because self is defined as *const @This(). Currently there’s no automatic pointer dereferencing. I should change that.

1 Like