Better optionals ergonomics

we can unwrap multiple optionals using the helper function that takes a tuple and use destructuring for convenience

var opt_a: ?i32 = null;
var opt_b: ?f32 = 2.2;
if (unwrapAll(.{ opt_a, opt_b })) |unwrapped| {
    var a, var b = unwrapped;
    std.debug.print("a = {}, b = {}\n", .{ a, b });
} else {
    std.debug.print("unwrap failed", .{});
}

It was fun to implement it using refiy type

fn UnwrappedType(comptime T: type) type {
    switch (@typeInfo(T)) {
        .Struct => |struct_info| {
            var unwrapped_fields: [struct_info.fields.len]std.builtin.Type.StructField = undefined;
            inline for (struct_info.fields, 0..) |field, i| {
                switch (@typeInfo(field.type)) {
                    .Optional => |field_info| {
                        unwrapped_fields[i] = .{
                            .name = field.name,
                            .type = field_info.child,
                            .default_value = field.default_value,
                            .is_comptime = field.is_comptime,
                            .alignment = 0,
                        };
                    },
                    else => @compileError("all fields must be optional type!"),
                }
            }

            return @Type(.{
                .Struct = .{
                    .layout = .Auto,
                    .fields = &unwrapped_fields,
                    .decls = &.{},
                    .is_tuple = true,
                },
            });
        },
        else => @compileError("parameter must be struct type!"),
    }
}

fn unwrapAll(tuple: anytype) ?UnwrappedType(@TypeOf(tuple)) {
    var result: UnwrappedType(@TypeOf(tuple)) = undefined;
    inline for (tuple, 0..) |opt_field, i| {
        if (opt_field) |field| {
            result[i] = field;
        } else {
            break;
        }
    } else {
        return result;
    }
    return null;
}
8 Likes