Interfacil - A Zig Package for Interfaces

Hey! I made my very first package.

I’ve always been frustrated by the lack of interfaces in the Zig language, especially after having a taste of Rust’s traits. However, knowing Zig’s take on simplicity, and where it stands in its roadmap, I understand why it can’t be there yet (maybe ever).

But with the help of a few seemingly under-appreciated features like usingnamespace, the lazy semantic analysis, the absence of actual distinction between methods and functions, I’ve made Interfacil.

Here’s a little snippet:

const Vector2D = struct {
    x: i32 = 0,
    y: i32 = 0,

    pub usingnamespace interfacil.Equivalent(Vector2D, .{});
};

test {
    const v1 = Vector2D{};
    const v2 = Vector2D{ .y = -42 };
    const v3 = v1;
    try expect(!v1.eq(v2));
    try expect(!v2.eq(v3));
    try expect(v3.eq(v1));
}

I hope you’ll enjoy it and find it useful for your own projects and productivity!

I’m a hobbyist programmer, and it’s my very first published project. I’ll be grateful for any feedback :slight_smile:

8 Likes

Really Nice Idea, I’m trying to build something from Rust too, I would really love to have a Result in Zig with the Some or None, and having nice payload attached to none, etc.

1 Like

There’s one in my misc.zig file. I also thought I miggt use it in the future.

1 Like

But you will loose try and catch. The challenge is to retain Zig’s excellent error propagating ergonomics while carrying error context (payload) upstack.

2 Likes

You don’t completly lose catch:

var fail: Fail = undefined;
const pass = result.nab(&fail) orelse {
    // do something with `fail`
};

That’s dirtier because it pollute the namespace, but I find it good enough. As for try I don’t think anything could come close unfortunately.

1 Like

I know but to be honest I’d like to have also an error type which can carry payload too, wouldn’t be nice to return the exact values of the inputs that were passed to your functions and failed ? or have at least a more descriptive message. Even for checking inputs after a function returns, I’d really like to have a little switch or if payload capture on some and none, or ok and err or something similar.

In this case I return error context through the last argument (a mutable pointer)

// Ziggish pseudocode
var err_ctx = ErrCtx.init(allocator);
pub fn bar() !void {
    try foo(arg1, arg2, &err_ctx) catch |err| {
         // do something with err and err_ctx.
}

I’m afraid, there is no such construction in Zig.
try foo(); is just a sugar for foo() catch |err| return err;.

This post links to an issue that discusses errors with payloads:

The issue is Allow returning a value with an error

This is what you do manually yourself. Zig does not support error payloads but you can mimic it if, by convention, you define your functions with an extra argument (a mutable pointer) to error context. In this case try works as expected and the caller can inspect err_ctx together with err. It is a workaround.

I think what @dee0xeed is saying is that you cannot use try foo() catch ... – this does not exist in Zig. You either do try foo() or foo() catch |err| ....

1 Like

Yes, absolutely right!
I think it was just a C++/Java habit :slight_smile:

What I am saying is that the function that generates the error puts some payload in err_cxt via catch, then the error propagates up-stack over (multiple) try and finally being handled in main by another catch.
This technique is described in Allow returning a value with an error · Issue #2647 · ziglang/zig · GitHub as the “wordaround 2: Out parameter”. It is not perfect, quite ugly but better than nothing.

EDIT: Just found a well written article describing advanced version of the OUT-parameter technique on zig.news

Wait… it’s not possible to write try foo() catch {} in Zig, is it?

const std = @import("std");

const Context = struct {
    a: u32 = 0,
    b: u32 = 0,
};

fn bar(err_ctx: *Context) !void {
    err_ctx.a = 7;
    err_ctx.b = 8;
    return error.Whack;
}

pub fn main() !void {
    var ctx = Context{};
    try bar(&ctx) catch |err| {
        std.debug.print("{} {} {}\n", err, ctx.a, ctx.b);
    };
}
$ zig build-exe s.zig 
s.zig:17:19: error: expected error union type, found 'void'
    try bar(&ctx) catch |err| {
    ~~~~~~~~~~~~~~^~~~~

You either catch or try but not both in the same statement.

foo(a,b, &err_ctx) catch |err| {
    err_ctx.* = error_payload_you_want_to_store;
    return err;
}
```
Then you can use `try` in the caller `bar(x,y,&err_ctx)` to bubble up-stack  and finally catch it where you want to handle it. The "ugliness" of this method is that you either modify signature of all the error-prone functions to include an extra `err_ctx` argument or keep `err_ctx` object global. 

My point is that it is possible  to deal with error payloads in Zig even without proper language support. It is just a bit ugly and non-ergonomic.

Amen! Now look once again what you’ve written:

my bad. lousy fingers on a small phone screen and copy-paste equals disaster.

2 Likes