Use `trait` to improve the user experience of `anytype`

vftrait


A compile-time library for duck type checking.
URL: Verafahn/vftrait

idea from Discussion about |T: Trait|.I think this is a good practice, so I wrote this project.

This project implements a complete satisfyTrait function. After introducing it, you can define a trait (actually a struct) in the project to constrain anytype. I think this can also be considered as an implementation of compile time polymorphism.

satisfyTrait is not simply determining whether a type has the same members as a trait, but rather whether T has a subset that is isomorphic to the trait. This is a more rigorous duck type check than name check.

Example

Let’s implement an iterator trait. It’s simple, just need one association type and one method:

pub const Iterator = struct {
    pub const Item = type;

    pub fn next(self: *@This()) ?Item {
        _ = self;
        @compileError("");
    }
};

I can implement a number generator. Just like a..b syntax.

pub const Generator = struct {
    pub const Item = u32;

    start: Item,
    end: Item,

    pub fn init(start: Item, end: Item) @This() {
        return .{ .start = start, .end = end };
    }

    pub fn next(self: *@This()) ?Item {
        if (self.start > self.end) return null;
        const result = self.start;
        self.start += 1;
        return result;
    }
};

I don’t need to add anything extra. Now Generator has met Iterator’s requirements. Next, I will write another function with trait constraints:

pub fn foreach(iter: anytype, callback: anytype) R: {
    const T = @typeInfo(@TypeOf(iter)).pointer.child;
    trait.assertSatisfyTrait(IteratorTrait, T);
    std.debug.assert(@TypeOf(callback) == fn (T.Item) void);
    break :R void;
} {
    while (iter.next()) |item| {
        callback(item);
    }
}

Write a simple code using them:

pub fn main(init: std.process.Init) !void {
    _ = init;

    var iter = Generator.init(0, 10);
    foreach(&iter, struct {
        pub fn lambda(item: u16) void {
            std.debug.print("{}\n", .{item});
        }
    }.lambda);
}

Not just one, I can write any type that satisfies the Iterator, and they can all be called through a foreach!

Supported Zig versions

zig 0.16.0

AI / LLM usage disclosure

I used AI to refactor my code (because it was written too messy).

6 Likes

Funny, I’m working on something very similar.

I have added clearer error messages to it. Now you can discover the location of the error through the error message.