为@call 构建可变长度参数数组
Build variable length arguments array for @call
我最近开始学习 Zig。
作为一个小项目,我想实现一个小型 QuickCheck [1] 风格的帮助程序库来编写随机测试。
但是,我不知道如何编写一种通用方法来调用具有任意数量参数的函数。
这是一个简化版本,可以测试带有两个参数的函数:
const std = @import("std");
const Prng = std.rand.DefaultPrng;
const Random = std.rand.Random;
const expect = std.testing.expect;
// the thing we want to test
fn some_property(a: u64, b: u64) !void {
var tmp: u64 = undefined;
var c1 = @addWithOverflow(u64, a, b, &tmp);
var c2 = @addWithOverflow(u64, a, b, &tmp);
expect(c1 == c2);
}
// helper for generating random arguments for the function under test
fn gen(comptime T: ?type, rnd: Random) (T orelse undefined) {
switch (T orelse undefined) {
u64 => return rnd.int(u64),
f64 => return rnd.float(f64),
else => @compileError("unsupported type"),
}
}
/// tests if 'property' holds.
fn for_all(property: anytype) !void {
var rnd = Prng.init(0);
const arg_types = @typeInfo(@TypeOf(property)).Fn.args;
var i: usize = 0;
while (i < 100) {
var a = gen(arg_types[0].arg_type, rnd.random());
var b = gen(arg_types[1].arg_type, rnd.random());
var args = .{a, b}; // <-- how do I build args for functions with any number of arguments?
try @call(.{}, property, args);
i += 1;
}
}
test "test" {
try for_all(some_property);
}
我尝试了几种不同的方法,但我无法弄清楚如何让上述代码适用于具有任意数量参数的函数。
我尝试过的事情:
- 制作
args
一个数组并用inline for
循环填充它。无效,因为 []anytype
不是有效类型。
- 使用一些 comptime 魔法来构建一个结构类型,其字段包含
@call
的参数。这在编译器中命中了一个 TODO:error: TODO: struct args
.
- 编写 return 适当参数元组调用的通用函数。我不太喜欢这个,因为你需要一个函数来支持你想要支持的每一个参数。但它似乎无论如何都不起作用,因为
antype
不是有效的 return 类型。
我正在使用 Zig 0.9.1。
如有任何见解,我们将不胜感激。
这可以通过 std.meta.ArgsTuple
完成(在 zig 标准库的 this file 中定义)
const Args = std.meta.ArgsTuple(@TypeOf(property));
var i: usize = 0;
while (i < 1000) : (i += 1) {
var args: Args = undefined;
inline for (std.meta.fields(Args)) |field, index| {
args[index] = gen(field.field_type, rnd.random());
}
try @call(.{}, property, args);
}
它在内部工作的方式是用 @Type()
构造一个元组类型。然后我们可以用值填充它并用它来调用函数。
我最近开始学习 Zig。 作为一个小项目,我想实现一个小型 QuickCheck [1] 风格的帮助程序库来编写随机测试。
但是,我不知道如何编写一种通用方法来调用具有任意数量参数的函数。
这是一个简化版本,可以测试带有两个参数的函数:
const std = @import("std");
const Prng = std.rand.DefaultPrng;
const Random = std.rand.Random;
const expect = std.testing.expect;
// the thing we want to test
fn some_property(a: u64, b: u64) !void {
var tmp: u64 = undefined;
var c1 = @addWithOverflow(u64, a, b, &tmp);
var c2 = @addWithOverflow(u64, a, b, &tmp);
expect(c1 == c2);
}
// helper for generating random arguments for the function under test
fn gen(comptime T: ?type, rnd: Random) (T orelse undefined) {
switch (T orelse undefined) {
u64 => return rnd.int(u64),
f64 => return rnd.float(f64),
else => @compileError("unsupported type"),
}
}
/// tests if 'property' holds.
fn for_all(property: anytype) !void {
var rnd = Prng.init(0);
const arg_types = @typeInfo(@TypeOf(property)).Fn.args;
var i: usize = 0;
while (i < 100) {
var a = gen(arg_types[0].arg_type, rnd.random());
var b = gen(arg_types[1].arg_type, rnd.random());
var args = .{a, b}; // <-- how do I build args for functions with any number of arguments?
try @call(.{}, property, args);
i += 1;
}
}
test "test" {
try for_all(some_property);
}
我尝试了几种不同的方法,但我无法弄清楚如何让上述代码适用于具有任意数量参数的函数。
我尝试过的事情:
- 制作
args
一个数组并用inline for
循环填充它。无效,因为[]anytype
不是有效类型。 - 使用一些 comptime 魔法来构建一个结构类型,其字段包含
@call
的参数。这在编译器中命中了一个 TODO:error: TODO: struct args
. - 编写 return 适当参数元组调用的通用函数。我不太喜欢这个,因为你需要一个函数来支持你想要支持的每一个参数。但它似乎无论如何都不起作用,因为
antype
不是有效的 return 类型。
我正在使用 Zig 0.9.1。
如有任何见解,我们将不胜感激。
这可以通过 std.meta.ArgsTuple
完成(在 zig 标准库的 this file 中定义)
const Args = std.meta.ArgsTuple(@TypeOf(property));
var i: usize = 0;
while (i < 1000) : (i += 1) {
var args: Args = undefined;
inline for (std.meta.fields(Args)) |field, index| {
args[index] = gen(field.field_type, rnd.random());
}
try @call(.{}, property, args);
}
它在内部工作的方式是用 @Type()
构造一个元组类型。然后我们可以用值填充它并用它来调用函数。