#Traits / Interfaces framework

1 messages · Page 1 of 1 (latest)

civic matrix
#

Im attempting to do something like the following, but struggling quite a bit:


pub const MyTrait = Trait(struct {
    fn foo(self: *anyopaque) void;
    fn bar(self: *anyopaque, x: i32) i32;
});

const A = struct { val: i32 };
const B = struct { val: i32 };

comptime {
    MyTrait.impl(A, struct {
        fn foo(self: *anyopaque) void {
            const s: *A = @ptrCast(self);
            std.debug.print("A.foo {}\n", .{s.val});
        }
        fn bar(self: *anyopaque, x: i32) i32 {
            const s: *A = @ptrCast(self);
            return s.val + x;
        }
    });

    MyTrait.impl(B, struct {
        fn foo(self: *anyopaque) void {
            const s: *B = @ptrCast(self);
            std.debug.print("B.foo {}\n", .{s.val * 2});
        }
        fn bar(self: *anyopaque, x: i32) i32 {
            const s: *B = @ptrCast(self);
            return (s.val * 2) + x;
        }
    });
}

pub fn main() void {
    var a = A{ .val = 3 };
    var b = B{ .val = 4 };

    const list = [_]MyTrait{
        MyTrait.up(A, &a),
        MyTrait.up(B, &b),
    };

    for (list) |o| {
        o.vtable.foo(o.instance);
        std.debug.print("bar = {}\n", .{o.vtable.bar(o.instance, 5)});
    }

    const a_ptr = MyTrait.down(A, list[0]);
    std.debug.print("down-cast = {}\n", .{a_ptr.val});
}
#

i cant post the dang Trait fn because its too long…

#

part 1

const std = @import("std");

pub fn Trait(comptime Interface: type) type {
    const decls = std.meta.declarations(Interface);

    const VTable = blk: {
        var fs: [decls.len]std.builtin.Type.StructField = undefined;
        inline for (decls, 0..) |d, i| {
            const Ty = @TypeOf(@field(Interface, d.name));
            fs[i] = .{
                .name = d.name,
                .type = Ty,
                .default_value = null,
                .is_comptime = false,
                .alignment = @alignOf(Ty),
            };
        }
        break :blk @Type(.{
            .Struct = .{
                .layout = .Auto,
                .fields = &fs,
                .decls = &.{},
                .is_tuple = false,
            },
        });
    };

    const ImplEntry = struct { concrete: type, vt: VTable };
    comptime var impls: []ImplEntry = &.{};

#

part 2

const Fns = struct {
        fn registerImpl(comptime T: type, comptime Impl: type) void {
            var vt: VTable = undefined;
            inline for (decls) |d| {
                if (!@hasDecl(Impl, d.name))
                    @compileError(@typeName(Impl) ++ " missing '" ++ d.name ++ "'");
                @field(vt, d.name) = @field(Impl, d.name);
            }
            const n = impls.len;
            var tmp: [n + 1]ImplEntry = undefined;
            inline for (impls, 0..) |e, i| tmp[i] = e;
            tmp[n] = .{ .concrete = T, .vt = vt };
            impls = tmp[0..];
        }

        fn vtFor(comptime T: type) *const VTable {
            inline for (impls) |e| if (e.concrete == T) return &e.vt;
            @compileError("Trait not implemented for " ++ @typeName(T));
        }
    };

    return struct {
        instance: *anyopaque,
        vtable: *const VTable,

        pub fn impl(comptime T: type, comptime Impl: type) void {
            Fns.registerImpl(T, Impl);
        }
        pub fn up(comptime T: type, ptr: *T) @This() {
            return .{ .instance = ptr, .vtable = Fns.vtFor(T) };
        }
        pub fn down(comptime T: type, obj: @This()) *T {
            std.debug.assert(obj.vtable == Fns.vtFor(T), "down-cast mismatch");
            return @ptrCast(obj.instance);
        }
    };
}

stable summit
#

isnt composition enough? using tagged unions you can get quite far and have easy to follow and read code, if you really want interfaces thrn this video might be helpful:

https://youtu.be/2Q8gB2OXB2E

Hello, this is ComputerBread,
Today, I wanted to share what I learned while making Zig interfaces.
So that's the video, we learn about zig interfaces, about anyopaque and data alignment.

zig 0.14.0
Cool stuff, hope you will enjoy!

Zig playlist: https://www.youtube.com/playlist?list=PLuJfrVx3aQbHUBcDnfJ3jZJmPfgjS3B-Y
Leetcode playlist: https...

▶ Play video
stark badge
civic matrix
#

i want to generate a struct and dynamically create functions on it which doesnt seem supported. any word on if/when that’ll be a thing?

stark badge
#

that is very unlikely to happen, zig's comptime is purposefully constricted. Surely there is some other way to approach that situation

civic matrix
#

im giving into manually defining the dyn dispatch struct, then using a comptime func to verify a given type has the appropriate functions. got a weird issue though:

zig version 0.14.1 ``` const decls = switch (@typeInfo(base)) {
.struct => |s| s.decls,
else => unreachable,
};

traits.zig:15:45: error: expected '}', found '.'
    const decls = switch (@typeInfo(base)) {
```const decls = switch (@typeInfo(base)) {
    .Struct => |s| s.decls,
    else => unreachable,
};
```traits.zig:16:10: error: no field named 'Struct' in enum '@typeInfo(builtin.Type).@"union".tag_type.?'
        .Struct => |s| s.decls,
silver pier
#

You need to use raw identifier syntax i.e. .@"struct"

civic matrix
#

ah.

new question: any way for me to add to an array during compile time? i need a global list basically derived from generated types.

stark badge
#

yea, there is the array concat operator ++, that's one way to build an array during comptime

#

[3]u8{1, 2, 3} ++ [2]u8{4, 5}

worthy slate
#

Yeah I honestly just declare the dyn disp structs by hand, the boilerplate isn't that bad.

#

You can also leverage the build system for things where comptime isn't getting the job done

#

and do like some very basic ad-hoc code generation and have it inlined

civic matrix
#

man the comment size limit makes showing code super painful. i have a ‘trait’ solution that is small just too many chars to post here apparently.

It’d help explain the direction im going, which is to define ‘traits’ (dyn dispatch structs), types (structs), and add them to a registry which creates runtime data. i also provided a comptime ‘verify’ utility which looks like verify(T, .{Drawable})