Here’s another approach. Given two functions, func
and retry
, we’ll create a third function that repeatedly calls func
until it succeeds or retry
returns false:
const std = @import("std");
pub fn hello(a: u32, b: u32) !void {
std.debug.print("Hello world {d}\n", .{a + b});
return error.shaka_when_the_walls_fell;
}
pub fn main() !void {
const Retry = struct {
const allowed = .{
error.the_beast_at_tanagra,
error.shaka_when_the_walls_fell,
};
const max = 10;
attempt: usize = 0,
fn onError(self: *@This(), err: anyerror) bool {
if (self.attempt <= max) {
if (std.mem.indexOfScalar(anyerror, &allowed, err) != null) {
self.attempt += 1;
return true;
}
}
return false;
}
};
const retryHello = Retriable(hello, Retry.onError);
try retryHello(123, 456);
}
Doing things this way is more flexible. It gives you the ability to introduce a delay mechanism, for example.
And here’s the code for Retriable()
:
pub fn Retriable(func: anytype, retry: anytype) @TypeOf(func) {
const f = @typeInfo(@TypeOf(func)).Fn;
const r = @typeInfo(@TypeOf(retry)).Fn;
const RT = f.return_type.?;
const RCPT = r.params[0].type.?;
const RCT = @typeInfo(RCPT).Pointer.child;
const PT = comptime extract: {
var Types: [f.params.len]type = undefined;
for (f.params, 0..) |param, index| {
Types[index] = param.type.?;
}
break :extract Types;
};
const cc = f.calling_convention;
const ns = struct {
fn call(args: anytype) RT {
var retry_context: ?RCT = null;
while (true) {
if (@call(.auto, func, args)) |result| {
return result;
} else |err| {
if (retry_context == null) retry_context = .{};
if (!retry(&retry_context.?, err)) {
return err;
}
}
}
}
fn call0() callconv(cc) RT {
return call(.{});
}
fn call1(a0: PT[0]) callconv(cc) RT {
return call(.{a0});
}
fn call2(a0: PT[0], a1: PT[1]) callconv(cc) RT {
return call(.{ a0, a1 });
}
fn call3(a0: PT[0], a1: PT[1], a2: PT[2]) callconv(cc) RT {
return call(.{ a0, a1, a2 });
}
fn call4(a0: PT[0], a1: PT[1], a2: PT[2], a3: PT[3]) callconv(cc) RT {
return call(.{ a0, a1, a2, a3 });
}
fn call5(a0: PT[0], a1: PT[1], a2: PT[2], a3: PT[3], a4: PT[4]) callconv(cc) RT {
return call(.{ a0, a1, a2, a3, a4 });
}
fn call6(a0: PT[0], a1: PT[1], a2: PT[2], a3: PT[3], a4: PT[4], a5: PT[5]) callconv(cc) RT {
return call(.{ a0, a1, a2, a3, a4, a5 });
}
fn call7(a0: PT[0], a1: PT[1], a2: PT[2], a3: PT[3], a4: PT[4], a5: PT[5], a6: PT[6]) callconv(cc) RT {
return call(.{ a0, a1, a2, a3, a4, a5, a6 });
}
fn call8(a0: PT[0], a1: PT[1], a2: PT[2], a3: PT[3], a4: PT[4], a5: PT[5], a6: PT[6], a7: PT[7]) callconv(cc) RT {
return call(.{ a0, a1, a2, a3, a4, a5, a6, a7 });
}
};
const caller_name = std.fmt.comptimePrint("call{d}", .{f.params.len});
if (!@hasDecl(ns, caller_name)) {
@compileError("Too many arguments");
}
return @field(ns, caller_name);
}
The function pyramid at the bottom is sort of goofy but there’s no other way to implement this type of function transform at the moment.