Skip to content

Instantly share code, notes, and snippets.

@adrusi
Created June 15, 2020 19:04
Show Gist options
  • Save adrusi/c32e8133914fab5800d0eea7e3d6be15 to your computer and use it in GitHub Desktop.
Save adrusi/c32e8133914fab5800d0eea7e3d6be15 to your computer and use it in GitHub Desktop.
Userland implementation of closures in Zig
const std = @import("std");
const assert = std.debug.assert;
pub fn closure(bindings: var) ClosureInternal(@TypeOf(bindings)) {
return ClosureInternal(@TypeOf(bindings)) { .ctx = bindings };
}
fn ClosureInternal(comptime Spec: type) type {
comptime {
const spec_tinfo = @typeInfo(Spec);
assert(spec_tinfo == .Struct);
for (spec_tinfo.Struct.fields) |field| {
assert(field.default_value == null);
}
assert(spec_tinfo.Struct.decls.len == 1);
const call_decl = spec_tinfo.Struct.decls[0];
assert(call_decl.is_pub);
assert(std.mem.eql(u8, call_decl.name, "call"));
const call = Spec.call;
const call_tinfo = @typeInfo(@TypeOf(call));
assert(call_tinfo == .Fn);
assert(!call_tinfo.Fn.is_generic);
assert(call_tinfo.Fn.args.len >= 1);
assert(call_tinfo.Fn.args[0].arg_type.? == *const Spec);
var arg_types: [call_tinfo.Fn.args.len - 1]type = undefined;
for (call_tinfo.Fn.args[1..]) |arg, i| {
arg_types[i] = arg.arg_type.?;
}
const RetType = call_tinfo.Fn.return_type.?;
return Closure(Spec, arg_types[0..], RetType);
}
}
pub fn Closure(comptime Ctx: type, comptime arg_types: []type, comptime RetType: type) type {
return struct {
ctx: Ctx,
pub fn call(self: *const @This(), args: var) RetType {
comptime {
assert(args.len == arg_types.len);
for (args) |_, i| {
assert(@TypeOf(args[i]) == arg_types[i]);
}
}
return @call(.{}, Ctx.call, .{ &self.ctx } ++ args);
}
};
}
test "closures" {
var x: i32 = 60;
const foo = closure(struct {
x: *i32,
pub fn call(self: *const @This(), y: i32) i32 {
self.x.* += y;
return 420;
}
} { .x = &x });
assert(foo.call(.{ @as(i32, 9) }) == 420);
assert(x == 69);
}
@Hanaasagi
Copy link

Hi, thank you for sharing this. To help more people, I updated this code to make it compile under Zig 0.11. Following is a diff patch

diff --git a/src/main.zig b/src/main.zig
index 2277d63..a5fabfd 100644
--- a/src/main.zig
+++ b/src/main.zig
@@ -1,8 +1,8 @@
 const std = @import("std");
 const assert = std.debug.assert;
 
-pub fn closure(bindings: var) ClosureInternal(@TypeOf(bindings)) {
-    return ClosureInternal(@TypeOf(bindings)) { .ctx = bindings };
+pub fn closure(bindings: anytype) ClosureInternal(@TypeOf(bindings)) {
+    return ClosureInternal(@TypeOf(bindings)){ .ctx = bindings };
 }
 
 fn ClosureInternal(comptime Spec: type) type {
@@ -23,12 +23,12 @@ fn ClosureInternal(comptime Spec: type) type {
         const call_tinfo = @typeInfo(@TypeOf(call));
         assert(call_tinfo == .Fn);
         assert(!call_tinfo.Fn.is_generic);
-        assert(call_tinfo.Fn.args.len >= 1);
-        assert(call_tinfo.Fn.args[0].arg_type.? == *const Spec);
+        assert(call_tinfo.Fn.params.len >= 1);
+        assert(call_tinfo.Fn.params[0].type.? == *const Spec);
 
-        var arg_types: [call_tinfo.Fn.args.len - 1]type = undefined;
-        for (call_tinfo.Fn.args[1..]) |arg, i| {
-            arg_types[i] = arg.arg_type.?;
+        var arg_types: [call_tinfo.Fn.params.len - 1]type = undefined;
+        for (call_tinfo.Fn.params[1..], 0..) |arg, i| {
+            arg_types[i] = arg.type.?;
         }
 
         const RetType = call_tinfo.Fn.return_type.?;
@@ -41,14 +41,14 @@ pub fn Closure(comptime Ctx: type, comptime arg_types: []type, comptime RetType:
     return struct {
         ctx: Ctx,
 
-        pub fn call(self: *const @This(), args: var) RetType {
+        pub fn call(self: *const @This(), args: anytype) RetType {
             comptime {
                 assert(args.len == arg_types.len);
-                for (args) |_, i| {
+                for (args, 0..) |_, i| {
                     assert(@TypeOf(args[i]) == arg_types[i]);
                 }
             }
-            return @call(.{}, Ctx.call, .{ &self.ctx } ++ args);
+            return @call(.auto, Ctx.call, .{&self.ctx} ++ args);
         }
     };
 }
@@ -62,8 +62,8 @@ test "closures" {
             self.x.* += y;
             return 420;
         }
-    } { .x = &x });
+    }{ .x = &x });
 
-    assert(foo.call(.{ @as(i32, 9) }) == 420);
+    assert(foo.call(.{@as(i32, 9)}) == 420);
     assert(x == 69);
 }

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment