Bercriber's Blog

zigでinterface的なやつ

fieldParentPtrイディオムは極めてシンプルな実装になっている。しかしfieldParentPtrイディオムはLLVMの最適化と相性が悪いらしい。zig ver0.9からはAllocgateと呼ばれる手法でstd.mem.Allocatorやstd.rand.Randomが多相化されている。*anyopaqueを使って型を無視することで動的に実装を選択できるようになっている。ええんかそれで。標準ライブラリでそれやってるくらいならそのうちに言語機能にtraitなりinterfaceなり実装されそうな気がするが、これの構文糖みたいなもんになるのだろうか。

const std = @import("std");

/// Use @fieldParentPtr
/// https://zig.news/david_vanderson/interfaces-in-zig-o1c
const FooI = struct {
    fooFn: fn(*FooI) void,
    fn foo(self: *FooI) void {
        self.fooFn(self);
    }
};

const Bar = struct {
    sym: u8,
    fooI: FooI,
    fn init() Bar {
        return .{ .sym = 0xff, .fooI = FooI { .fooFn = fooFn } };
    }
    fn fooFn(fooI: *FooI) void {
        const self = @fieldParentPtr(Bar, "fooI", fooI);
        std.debug.print("foo({}) from Bar\n", .{self.sym});
    }
};

/// Allocgate
/// https://pithlessly.github.io/allocgate.html
/// https://github.com/ziglang/zig/blob/master/lib/std/mem/Allocator.zig
/// https://github.com/ziglang/zig/blob/master/lib/std/rand.zig
const HogeI = struct {
    ptr: *anyopaque,
    hogeFn: fn (ptr: *anyopaque) void,
    fn init(pointer: anytype, comptime hogeFn: fn (ptr: @TypeOf(pointer)) void) HogeI {
        const Ptr = @TypeOf(pointer);
        std.debug.assert(@typeInfo(Ptr) == .Pointer); // Must be a pointer
        std.debug.assert(@typeInfo(Ptr).Pointer.size == .One); // Must be a single-item pointer
        std.debug.assert(@typeInfo(@typeInfo(Ptr).Pointer.child) == .Struct); // Must point to a struct
        const gen = struct {
            fn hoge(ptr: *anyopaque) void {
                const alignment = @typeInfo(Ptr).Pointer.alignment;
                const self = @ptrCast(Ptr, @alignCast(alignment, ptr));
                @call(.{.modifier = .always_inline}, hogeFn, .{self});
            }
        };
        return .{
            .ptr = pointer,
            .hogeFn = gen.hoge
        };
    }

    fn hoge(self: HogeI) void {
        self.hogeFn(self.ptr);
    }
};

const Fuga = struct {
    sym: u8,
    fn init() Fuga {
        return .{
            .sym = 0xff / 2
        };
    }

    fn hogeI(self: *Fuga) HogeI {
        return HogeI.init(self, hogeFn);
    }

    fn hogeFn(self: *Fuga) void {
        std.debug.print("hoo({}) from Fuga\n", .{self.sym});
    }
};



pub fn main() !void {
    var bar = Bar.init();
    bar.fooI.foo();

    var fuga = Fuga.init();
    var hogeI = fuga.hogeI();
    hogeI.hoge();
}