mirror of
https://github.com/zigzap/zap.git
synced 2025-10-20 23:24:09 +00:00
BoundFunction POLYMORPHIC!!!
This commit is contained in:
parent
6dd3c93a0f
commit
d4a2079542
1 changed files with 272 additions and 143 deletions
|
@ -1,125 +1,22 @@
|
||||||
const std = @import("std");
|
const std = @import("std");
|
||||||
|
|
||||||
/// Bind a function with specific signature to a method of any instance of a given type
|
/// Helper function that returns a function type with ArgType prepended to the
|
||||||
pub fn OldBind(func: anytype, instance: anytype) OldBound(@typeInfo(@TypeOf(instance)).pointer.child, func) {
|
/// function's args.
|
||||||
const Instance = @typeInfo(@TypeOf(instance)).pointer.child;
|
/// Example:
|
||||||
return OldBound(Instance, func).init(@constCast(instance));
|
/// Func = fn(usize) void
|
||||||
}
|
/// ArgType = *Instance
|
||||||
|
/// --------------------------
|
||||||
pub fn OldBound(Instance: type, func: anytype) type {
|
/// Result = fn(*Instance, usize) void
|
||||||
// Verify Func is a function type
|
fn PrependFnArg(Func: type, ArgType: type) type {
|
||||||
const func_info = @typeInfo(@TypeOf(func));
|
|
||||||
if (func_info != .@"fn") {
|
|
||||||
@compileError("OldBound expexts a function type as second parameter");
|
|
||||||
}
|
|
||||||
|
|
||||||
// Verify first parameter is pointer to Instance type
|
|
||||||
const params = func_info.@"fn".params;
|
|
||||||
if (params.len == 0 or (params[0].type != *Instance and params[0].type != *const Instance)) {
|
|
||||||
@compileError("Function's first parameter must be " ++ @typeName(Instance) ++ " but got: " ++ @typeName(params[0].type.?));
|
|
||||||
}
|
|
||||||
|
|
||||||
return struct {
|
|
||||||
instance: *Instance,
|
|
||||||
|
|
||||||
const OldBoundFunction = @This();
|
|
||||||
|
|
||||||
pub fn call(self: OldBoundFunction, args: anytype) func_info.@"fn".return_type.? {
|
|
||||||
return @call(.auto, func, .{self.instance} ++ args);
|
|
||||||
}
|
|
||||||
|
|
||||||
// convenience init
|
|
||||||
pub fn init(instance_: *Instance) OldBoundFunction {
|
|
||||||
return .{ .instance = instance_ };
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
test "OldBound" {
|
|
||||||
const Person = struct {
|
|
||||||
name: []const u8,
|
|
||||||
_buf: [1024]u8 = undefined,
|
|
||||||
|
|
||||||
// takes const instance
|
|
||||||
pub fn greet(self: *const @This(), gpa: std.mem.Allocator, greeting: []const u8) ![]const u8 {
|
|
||||||
return std.fmt.allocPrint(gpa, "{s}, {s}!\n", .{ greeting, self.name });
|
|
||||||
}
|
|
||||||
|
|
||||||
// takes non-const instance
|
|
||||||
pub fn farewell(self: *@This(), message: []const u8) ![]const u8 {
|
|
||||||
return std.fmt.bufPrint(self._buf[0..], "{s}, {s}!\n", .{ message, self.name });
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
var alice: Person = .{ .name = "Alice" };
|
|
||||||
|
|
||||||
// creation variant a: manually instantiate
|
|
||||||
const bound_greet: OldBound(Person, Person.greet) = .{ .instance = &alice };
|
|
||||||
|
|
||||||
// creation variant b: call init function
|
|
||||||
const bound_farewell = OldBound(Person, Person.farewell).init(&alice);
|
|
||||||
|
|
||||||
const ta = std.testing.allocator;
|
|
||||||
const greeting = try bound_greet.call(.{ ta, "Hello" });
|
|
||||||
defer ta.free(greeting);
|
|
||||||
|
|
||||||
try std.testing.expectEqualStrings("Hello, Alice!\n", greeting);
|
|
||||||
try std.testing.expectEqualStrings("Goodbye, Alice!\n", try bound_farewell.call(.{"Goodbye"}));
|
|
||||||
}
|
|
||||||
|
|
||||||
test OldBind {
|
|
||||||
const Person = struct {
|
|
||||||
name: []const u8,
|
|
||||||
_buf: [1024]u8 = undefined,
|
|
||||||
|
|
||||||
// takes const instance
|
|
||||||
pub fn greet(self: *const @This(), gpa: std.mem.Allocator, greeting: []const u8) ![]const u8 {
|
|
||||||
return std.fmt.allocPrint(gpa, "{s}, {s}!\n", .{ greeting, self.name });
|
|
||||||
}
|
|
||||||
|
|
||||||
// takes non-const instance
|
|
||||||
pub fn farewell(self: *@This(), message: []const u8) ![]const u8 {
|
|
||||||
return std.fmt.bufPrint(self._buf[0..], "{s}, {s}!\n", .{ message, self.name });
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
var alice: Person = .{ .name = "Alice" };
|
|
||||||
|
|
||||||
const bound_greet = OldBind(Person.greet, &alice);
|
|
||||||
const bound_farewell = OldBind(Person.farewell, &alice);
|
|
||||||
|
|
||||||
const ta = std.testing.allocator;
|
|
||||||
const greeting = try bound_greet.call(.{ ta, "Hello" });
|
|
||||||
defer ta.free(greeting);
|
|
||||||
|
|
||||||
try std.testing.expectEqualStrings("Hello, Alice!\n", greeting);
|
|
||||||
try std.testing.expectEqualStrings("Goodbye, Alice!\n", try bound_farewell.call(.{"Goodbye"}));
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Creates a function type with instance pointer prepended to args
|
|
||||||
fn PrependFnArg(Func: type, Instance: type) type {
|
|
||||||
const InstancePtr = *Instance;
|
|
||||||
|
|
||||||
// Get the function type
|
|
||||||
const fn_info = @typeInfo(Func);
|
const fn_info = @typeInfo(Func);
|
||||||
if (fn_info != .@"fn") {
|
if (fn_info != .@"fn") @compileError("First argument must be a function type");
|
||||||
@compileError("Second argument must be a function");
|
|
||||||
}
|
|
||||||
|
|
||||||
// Create new parameter list with instance pointer prepended
|
|
||||||
comptime var new_params: [fn_info.@"fn".params.len + 1]std.builtin.Type.Fn.Param = undefined;
|
comptime var new_params: [fn_info.@"fn".params.len + 1]std.builtin.Type.Fn.Param = undefined;
|
||||||
new_params[0] = .{
|
new_params[0] = .{ .is_generic = false, .is_noalias = false, .type = ArgType };
|
||||||
.is_generic = false,
|
|
||||||
.is_noalias = false,
|
|
||||||
.type = InstancePtr,
|
|
||||||
};
|
|
||||||
|
|
||||||
// Copy original parameters
|
|
||||||
for (fn_info.@"fn".params, 0..) |param, i| {
|
for (fn_info.@"fn".params, 0..) |param, i| {
|
||||||
new_params[i + 1] = param;
|
new_params[i + 1] = param;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Return the new function type
|
|
||||||
return @Type(.{
|
return @Type(.{
|
||||||
.@"fn" = .{
|
.@"fn" = .{
|
||||||
.calling_convention = fn_info.@"fn".calling_convention,
|
.calling_convention = fn_info.@"fn".calling_convention,
|
||||||
|
@ -130,55 +27,287 @@ fn PrependFnArg(Func: type, Instance: type) type {
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
/// Bind functions like `fn(a: X, b: Y)` to an instance of a struct. When called, the instance's `pub fn(self: *This(), a: X, b: Y)` is called.
|
|
||||||
///
|
|
||||||
/// make callbacks stateful when they're not meant to be?
|
|
||||||
// pub fn Bound(Instance: type, Func: type, func: anytype) type {
|
|
||||||
pub fn Bind(Instance: type, Func: type) type {
|
|
||||||
|
|
||||||
// TODO: construct DFunc on-the-fly
|
// External Generic Interface (CallbackInterface)
|
||||||
|
pub fn CallbackInterface(comptime Func: type) type {
|
||||||
// Verify Func is a function type
|
|
||||||
const func_info = @typeInfo(Func);
|
const func_info = @typeInfo(Func);
|
||||||
if (func_info != .@"fn") {
|
if (func_info != .@"fn") @compileError("CallbackInterface expects a function type");
|
||||||
@compileError("Bound expexts a function type as second parameter");
|
if (func_info.@"fn".is_generic) @compileError("CallbackInterface does not support generic functions");
|
||||||
}
|
if (func_info.@"fn".is_var_args) @compileError("CallbackInterface does not support var_args functions");
|
||||||
|
|
||||||
|
const ArgsTupleType = std.meta.ArgsTuple(Func);
|
||||||
|
const ReturnType = func_info.@"fn".return_type.?;
|
||||||
|
const FnPtrType = *const fn (ctx: ?*const anyopaque, args: ArgsTupleType) ReturnType;
|
||||||
|
|
||||||
const InstanceMethod = PrependFnArg(Func, Instance);
|
|
||||||
return struct {
|
return struct {
|
||||||
instance: *Instance,
|
ctx: ?*const anyopaque,
|
||||||
foo: *const InstanceMethod,
|
callFn: FnPtrType,
|
||||||
|
pub const Interface = @This();
|
||||||
|
|
||||||
const BoundFunction = @This();
|
pub fn call(self: Interface, args: ArgsTupleType) ReturnType {
|
||||||
|
if (self.ctx == null) @panic("Called uninitialized CallbackInterface");
|
||||||
pub fn call(self: BoundFunction, args: anytype) func_info.@"fn".return_type.? {
|
if (ReturnType == void) {
|
||||||
return @call(.auto, self.foo, .{self.instance} ++ args);
|
self.callFn(self.ctx, args);
|
||||||
}
|
} else {
|
||||||
|
return self.callFn(self.ctx, args);
|
||||||
// convenience init
|
}
|
||||||
pub fn init(instance_: *Instance, foo_: *const InstanceMethod) BoundFunction {
|
|
||||||
return .{ .instance = instance_, .foo = foo_ };
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
test Bind {
|
pub fn Bind(Instance: type, Func: type) type {
|
||||||
|
const func_info = @typeInfo(Func);
|
||||||
|
if (func_info != .@"fn") @compileError("Bind expects a function type as second parameter");
|
||||||
|
if (func_info.@"fn".is_generic) @compileError("Binding generic functions is not supported");
|
||||||
|
if (func_info.@"fn".is_var_args) @compileError("Binding var_args functions is not currently supported");
|
||||||
|
|
||||||
|
const ReturnType = func_info.@"fn".return_type.?;
|
||||||
|
const OriginalParams = func_info.@"fn".params; // Needed for comptime loops
|
||||||
|
const ArgsTupleType = std.meta.ArgsTuple(Func);
|
||||||
|
const InstanceMethod = PrependFnArg(Func, *Instance);
|
||||||
|
const InterfaceType = CallbackInterface(Func);
|
||||||
|
|
||||||
|
return struct {
|
||||||
|
instance: *Instance,
|
||||||
|
method: *const InstanceMethod,
|
||||||
|
pub const BoundFunction = @This();
|
||||||
|
|
||||||
|
// Trampoline function using runtime tuple construction
|
||||||
|
fn callDetached(ctx: ?*const anyopaque, args: ArgsTupleType) ReturnType {
|
||||||
|
if (ctx == null) @panic("callDetached called with null context");
|
||||||
|
const self: *const BoundFunction = @ptrCast(@alignCast(ctx.?));
|
||||||
|
|
||||||
|
// 1. Define the tuple type needed for the call: .{*Instance, OriginalArgs...}
|
||||||
|
const CallArgsTupleType = comptime T: {
|
||||||
|
var tuple_fields: [OriginalParams.len + 1]std.builtin.Type.StructField = undefined;
|
||||||
|
// Field 0: *Instance type
|
||||||
|
tuple_fields[0] = .{
|
||||||
|
.name = "0",
|
||||||
|
.type = @TypeOf(self.instance),
|
||||||
|
.default_value_ptr = null,
|
||||||
|
.is_comptime = false,
|
||||||
|
.alignment = 0,
|
||||||
|
};
|
||||||
|
// Fields 1..N: Original argument types (use ArgsTupleType fields)
|
||||||
|
for (std.meta.fields(ArgsTupleType), 0..) |field, i| {
|
||||||
|
tuple_fields[i + 1] = .{
|
||||||
|
.name = std.fmt.comptimePrint("{d}", .{i + 1}),
|
||||||
|
.type = field.type,
|
||||||
|
.default_value_ptr = null,
|
||||||
|
.is_comptime = false,
|
||||||
|
.alignment = 0,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
break :T @Type(.{ .@"struct" = .{
|
||||||
|
.layout = .auto,
|
||||||
|
.fields = &tuple_fields,
|
||||||
|
.decls = &.{},
|
||||||
|
.is_tuple = true,
|
||||||
|
} });
|
||||||
|
};
|
||||||
|
|
||||||
|
// 2. Create and populate the tuple at runtime
|
||||||
|
var call_args_tuple: CallArgsTupleType = undefined;
|
||||||
|
@field(call_args_tuple, "0") = self.instance; // Set the instance pointer
|
||||||
|
|
||||||
|
// Copy original args from 'args' tuple to 'call_args_tuple'
|
||||||
|
comptime var i = 0;
|
||||||
|
inline while (i < OriginalParams.len) : (i += 1) {
|
||||||
|
const src_field_name = comptime std.fmt.comptimePrint("{}", .{i});
|
||||||
|
const dest_field_name = comptime std.fmt.comptimePrint("{}", .{i + 1});
|
||||||
|
@field(call_args_tuple, dest_field_name) = @field(args, src_field_name);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 3. Perform the call using the populated tuple
|
||||||
|
if (ReturnType == void) {
|
||||||
|
@call(.auto, self.method, call_args_tuple);
|
||||||
|
} else {
|
||||||
|
return @call(.auto, self.method, call_args_tuple);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn interface(self: *const BoundFunction) InterfaceType {
|
||||||
|
return .{ .ctx = @ptrCast(self), .callFn = &callDetached };
|
||||||
|
}
|
||||||
|
|
||||||
|
// Direct call convenience method using runtime tuple construction
|
||||||
|
pub fn call(self: *const BoundFunction, args: anytype) ReturnType {
|
||||||
|
// 1. Verify 'args' is the correct ArgsTupleType or compatible tuple literal
|
||||||
|
// (This check could be more robust if needed)
|
||||||
|
if (@TypeOf(args) != ArgsTupleType) {
|
||||||
|
// Attempt reasonable check for tuple literal compatibility
|
||||||
|
if (@typeInfo(@TypeOf(args)) != .@"struct" or !@typeInfo(@TypeOf(args)).@"struct".is_tuple) {
|
||||||
|
@compileError(std.fmt.comptimePrint(
|
||||||
|
"Direct .call expects arguments as a tuple literal compatible with {}, found type {}",
|
||||||
|
.{ ArgsTupleType, @TypeOf(args) },
|
||||||
|
));
|
||||||
|
}
|
||||||
|
// Further check field count/types if necessary
|
||||||
|
if (std.meta.fields(@TypeOf(args)).len != OriginalParams.len) {
|
||||||
|
@compileError(std.fmt.comptimePrint(
|
||||||
|
"Direct .call tuple literal has wrong number of arguments (expected {}, got {}) for {}",
|
||||||
|
.{ OriginalParams.len, std.meta.fields(@TypeOf(args)).len, ArgsTupleType },
|
||||||
|
));
|
||||||
|
}
|
||||||
|
// Could add type checks per field here too
|
||||||
|
}
|
||||||
|
|
||||||
|
// 2. Define the tuple type needed for the call: .{*Instance, OriginalArgs...}
|
||||||
|
const CallArgsTupleType = comptime T: {
|
||||||
|
var tuple_fields: [OriginalParams.len + 1]std.builtin.Type.StructField = undefined;
|
||||||
|
tuple_fields[0] = .{
|
||||||
|
.name = "0",
|
||||||
|
.type = @TypeOf(self.instance),
|
||||||
|
.default_value_ptr = null,
|
||||||
|
.is_comptime = false,
|
||||||
|
.alignment = 0,
|
||||||
|
};
|
||||||
|
for (std.meta.fields(ArgsTupleType), 0..) |field, i| {
|
||||||
|
tuple_fields[i + 1] = .{
|
||||||
|
.name = std.fmt.comptimePrint("{d}", .{i + 1}),
|
||||||
|
.type = field.type,
|
||||||
|
.default_value_ptr = null,
|
||||||
|
.is_comptime = false,
|
||||||
|
.alignment = 0,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
break :T @Type(.{ .@"struct" = .{
|
||||||
|
.layout = .auto,
|
||||||
|
.fields = &tuple_fields,
|
||||||
|
.decls = &.{},
|
||||||
|
.is_tuple = true,
|
||||||
|
} });
|
||||||
|
};
|
||||||
|
|
||||||
|
// 3. Create and populate the tuple at runtime
|
||||||
|
var call_args_tuple: CallArgsTupleType = undefined;
|
||||||
|
@field(call_args_tuple, "0") = self.instance;
|
||||||
|
|
||||||
|
comptime var i = 0;
|
||||||
|
inline while (i < OriginalParams.len) : (i += 1) {
|
||||||
|
const field_name = comptime std.fmt.comptimePrint("{}", .{i});
|
||||||
|
// Check if field exists in args (useful for struct literals, less for tuples)
|
||||||
|
// For tuple literals, direct access should work if type check passed.
|
||||||
|
// if (@hasField(@TypeOf(args), field_name)) { ... }
|
||||||
|
const dest_field_name = comptime std.fmt.comptimePrint("{}", .{i + 1});
|
||||||
|
@field(call_args_tuple, dest_field_name) = @field(args, field_name);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 4. Perform the call using the populated tuple
|
||||||
|
if (ReturnType == void) {
|
||||||
|
@call(.auto, self.method, call_args_tuple);
|
||||||
|
} else {
|
||||||
|
return @call(.auto, self.method, call_args_tuple);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn init(instance_: *Instance, method_: *const InstanceMethod) BoundFunction {
|
||||||
|
return .{ .instance = instance_, .method = method_ };
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
const testing = std.testing;
|
||||||
|
|
||||||
|
test "Bind Direct Call" {
|
||||||
const Person = struct {
|
const Person = struct {
|
||||||
name: []const u8,
|
name: []const u8,
|
||||||
_buf: [1024]u8 = undefined,
|
_buf: [1024]u8 = undefined,
|
||||||
|
pub fn speak(self: *@This(), msg: []const u8) ![]const u8 {
|
||||||
pub fn speak(self: *@This(), message: []const u8) ![]const u8 {
|
return std.fmt.bufPrint(&self._buf, "{s}: {s}", .{ self.name, msg });
|
||||||
return std.fmt.bufPrint(self._buf[0..], "{s} says: >>{s}!<<\n", .{ self.name, message });
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
const FuncSig = fn ([]const u8) anyerror![]const u8;
|
||||||
|
var p = Person{ .name = "Alice" };
|
||||||
|
const bound = Bind(Person, FuncSig).init(&p, &Person.speak);
|
||||||
|
const res = try bound.call(.{"Hi"}); // Pass tuple literal
|
||||||
|
try testing.expectEqualStrings("Alice: Hi", res);
|
||||||
|
}
|
||||||
|
|
||||||
|
test "BindInterface Call (External)" {
|
||||||
|
const Person = struct {
|
||||||
|
name: []const u8,
|
||||||
|
_buf: [1024]u8 = undefined,
|
||||||
|
pub fn speak(self: *@This(), message: []const u8) ![]const u8 {
|
||||||
|
return std.fmt.bufPrint(&self._buf, "{s} says: >>{s}!<<\n", .{ self.name, message });
|
||||||
|
}
|
||||||
|
};
|
||||||
const CallBack = fn ([]const u8) anyerror![]const u8;
|
const CallBack = fn ([]const u8) anyerror![]const u8;
|
||||||
|
var alice: Person = .{ .name = "Alice" };
|
||||||
|
const BoundSpeak = Bind(Person, CallBack);
|
||||||
|
const bound_speak = BoundSpeak.init(&alice, &Person.speak);
|
||||||
|
var alice_interface = bound_speak.interface();
|
||||||
|
const greeting = try alice_interface.call(.{"Hello"}); // Pass tuple literal
|
||||||
|
try testing.expectEqualStrings("Alice says: >>Hello!<<\n", greeting);
|
||||||
|
}
|
||||||
|
|
||||||
|
test "BindInterface Polymorphism (External)" {
|
||||||
|
const Person = struct {
|
||||||
|
name: []const u8,
|
||||||
|
_buf: [1024]u8 = undefined,
|
||||||
|
pub fn speak(self: *@This(), message: []const u8) ![]const u8 {
|
||||||
|
return std.fmt.bufPrint(&self._buf, "{s} says: >>{s}!<<\n", .{ self.name, message });
|
||||||
|
}
|
||||||
|
};
|
||||||
|
const Dog = struct {
|
||||||
|
name: []const u8,
|
||||||
|
_buf: [1024]u8 = undefined,
|
||||||
|
pub fn bark(self: *@This(), message: []const u8) ![]const u8 {
|
||||||
|
return std.fmt.bufPrint(&self._buf, "{s} barks: >>{s}!<<\n", .{ self.name, message });
|
||||||
|
}
|
||||||
|
};
|
||||||
|
const CallBack = fn ([]const u8) anyerror![]const u8;
|
||||||
|
const CbInterface = CallbackInterface(CallBack);
|
||||||
|
|
||||||
var alice: Person = .{ .name = "Alice" };
|
var alice: Person = .{ .name = "Alice" };
|
||||||
|
const bound_alice = Bind(Person, CallBack).init(&alice, &Person.speak);
|
||||||
|
const alice_interface = bound_alice.interface();
|
||||||
|
|
||||||
const bound_greet = Bind(Person, CallBack).init(&alice, &Person.speak);
|
var bob: Dog = .{ .name = "Bob" };
|
||||||
|
const bound_bob = Bind(Dog, CallBack).init(&bob, &Dog.bark);
|
||||||
|
const bob_interface = bound_bob.interface();
|
||||||
|
|
||||||
const greeting = try bound_greet.call(.{"Hello"});
|
const interfaces = [_]CbInterface{ alice_interface, bob_interface };
|
||||||
|
var results: [2][]const u8 = undefined;
|
||||||
|
for (interfaces, 0..) |iface, i| {
|
||||||
|
results[i] = try iface.call(.{"Test"});
|
||||||
|
} // Pass tuple literal
|
||||||
|
|
||||||
try std.testing.expectEqualStrings("Alice says: >>Hello!<<\n", greeting);
|
try testing.expectEqualStrings("Alice says: >>Test!<<\n", results[0]);
|
||||||
|
try testing.expectEqualStrings("Bob barks: >>Test!<<\n", results[1]);
|
||||||
|
}
|
||||||
|
|
||||||
|
test "Void Return Type (External Interface)" {
|
||||||
|
var counter: u32 = 0;
|
||||||
|
const Counter = struct {
|
||||||
|
count: *u32,
|
||||||
|
pub fn increment(self: *@This(), amount: u32) void {
|
||||||
|
self.count.* += amount;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
const Decrementer = struct {
|
||||||
|
count: *u32,
|
||||||
|
pub fn decrement(self: *@This(), amount: u32) void {
|
||||||
|
self.count.* -= amount;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
const IncrementFn = fn (u32) void;
|
||||||
|
const IncInterface = CallbackInterface(IncrementFn);
|
||||||
|
|
||||||
|
var my_counter = Counter{ .count = &counter };
|
||||||
|
const bound_inc = Bind(Counter, IncrementFn).init(&my_counter, &Counter.increment);
|
||||||
|
bound_inc.call(.{5});
|
||||||
|
try testing.expectEqual(@as(u32, 5), counter);
|
||||||
|
|
||||||
|
var my_dec = Decrementer{ .count = &counter };
|
||||||
|
const bound_dec = Bind(Decrementer, IncrementFn).init(&my_dec, &Decrementer.decrement);
|
||||||
|
|
||||||
|
const iface1 = bound_inc.interface();
|
||||||
|
const iface2 = bound_dec.interface();
|
||||||
|
const void_ifaces = [_]IncInterface{ iface1, iface2 };
|
||||||
|
|
||||||
|
void_ifaces[0].call(.{3}); // counter = 5 + 3 = 8
|
||||||
|
try testing.expectEqual(@as(u32, 8), counter);
|
||||||
|
void_ifaces[1].call(.{2}); // counter = 8 - 2 = 6
|
||||||
|
try testing.expectEqual(@as(u32, 6), counter);
|
||||||
}
|
}
|
||||||
|
|
Loading…
Add table
Reference in a new issue