From _InKryption (discord link). Requires 0.14.0:
const std = @import("std");
test "destructure by val" {
const b, const a = destructure(.{ .b, .a }, .{
.a = 3,
.b = std.testing.random_seed,
});
try std.testing.expectEqual(3, a);
try std.testing.expectEqual(std.testing.random_seed, b);
}
test "destructure by ref" {
const orig = .{
.a = 3,
.b = std.testing.random_seed,
};
const b, const a = destructure(.{ .b, .a }, &orig);
try std.testing.expectEqual(&orig.b, b);
try comptime std.testing.expectEqual(&orig.a, a);
}
pub inline fn destructure(
/// [n]std.meta.FieldEnum(@TypeOf(value))
comptime order: anytype,
value: anytype,
) Destructured(@TypeOf(value), order) {
const is_pointer = @typeInfo(@TypeOf(value)) == .pointer;
var result: Destructured(@TypeOf(value), order) = undefined;
inline for (order, 0..) |field_tag, i| {
const ptr = &@field(value, @tagName(field_tag));
result[i] = if (is_pointer) ptr else ptr.*;
}
return result;
}
pub fn DestructureOrder(T: type) type {
const Deref = switch (@typeInfo(T)) {
.pointer => |ptr_info| switch (ptr_info.size) {
.One => ptr_info.child,
else => T,
},
else => T,
};
return switch (@typeInfo(Deref)) {
.@"struct" => |info| [info.fields.len]std.meta.FieldEnum(Deref),
else => @compileError("Cannot destructure " ++ @typeName(Deref)),
};
}
pub fn Destructured(T: type, order: DestructureOrder(T)) type {
const Deref, //
const ptr_info //
= switch (@typeInfo(T)) {
.pointer => |ptr_info| switch (ptr_info.size) {
.One => .{ ptr_info.child, ptr_info },
else => unreachable,
},
else => .{ T, {} },
};
const is_pointer = @TypeOf(ptr_info) != void;
var fields: [order.len]std.builtin.Type.StructField = undefined;
for (
@typeInfo(@TypeOf(.{{}} ** order.len)).@"struct".fields, //
order,
0..,
) |ref_field, field_tag, i| {
const field_i = @intFromEnum(field_tag);
const field = &fields[i];
field.* = @typeInfo(Deref).@"struct".fields[field_i];
field.name = ref_field.name;
field.alignment = 0;
if (is_pointer) {
const dummy_ptr: T = @ptrFromInt(std.mem.alignBackward(usize, std.math.maxInt(usize), ptr_info.alignment));
field.type = @TypeOf(&@field(dummy_ptr, @tagName(field_tag)));
if (field.is_comptime) {
field.default_value = @typeInfo(@TypeOf(.{&@field(dummy_ptr, @tagName(field_tag))})).@"struct".fields[0].default_value;
} else {
field.default_value = null;
}
}
}
return @Type(.{ .@"struct" = .{
.layout = .auto,
.backing_integer = null,
.is_tuple = true,
.decls = &.{},
.fields = &fields,
} });
}
alternative
The technique above works for higher-order code, but if the destructuring code is supplying the names as in those tests, you can directly use the names.
NB. The technique above also enforces exhaustive destructuring, which this doesn't do:
const std = @import("std");
test "destructure values by name" {
const obj = .{ .a = 3, .b = 123 };
const b, const a = .{ obj.b, obj.a };
try std.testing.expectEqual(3, a);
try std.testing.expectEqual(123, b);
}
test "destructure refs by name" {
const obj = .{ .a = 3, .b = 123 };
const b, const a = .{ &obj.b, &obj.a };
try std.testing.expectEqual(3, a.*);
try std.testing.expectEqual(123, b.*);
try comptime std.testing.expectEqual(&obj.b, b);
try comptime std.testing.expectEqual(&obj.a, a);
}