🍒

Zig で環境変数を型安全に管理

2023/05/28に公開

はじめに

先週、私は Zig の comptime を学び、struct-env というライブラリを作成しました。

https://github.com/Hanaasagi/struct-env

このライブラリを使用すると、環境変数を自動的に対応する構造体のフィールドに逆シリアル化することができます。以下は使用例です。

main.zig
const std = @import("std");
const struct_env = @import("struct-env");

const MyEnv = struct {
    home: []const u8,
    foo: ?[]const u8,
    bar: []const u8 = "bar",
};

pub fn main() !void {
    const allocator = std.heap.page_allocator;
    const env = try struct_env.fromEnv(allocator, MyEnv);
    defer struct_env.free(allocator, env);

    std.debug.print("HOME is {s}\n", .{env.home});
    std.debug.print("FOO  is {any}\n", .{env.foo == null});
    std.debug.print("BAR  is {s}\n", .{env.bar});
}

実行後、HOMEFOOBAR の環境変数が自動的に構造体に注入されます。

$ zig build run
HOME is /home/guest
FOO  is true
BAR  is bar

Zig の comptime

ここからは、Zig の comptime に関する知識を共有します。

まず最初に、

  • @TypeOf は値の型を取得するためのものです。
  • @typeName は型の名前を取得するためのものです。
const std = @import("std");
const testing = std.testing;

const T = struct {
    a: i32,
};

pub fn main() !void {
    const t = T{ .a = 1 };

    try testing.expectEqual(T, @TypeOf(t));
    try testing.expect(std.mem.eql(u8, "main.T", @typeName(T)));
}
  • std.meta.Child を使用すると、複合型の子型を取得することができます。
    try testing.expectEqual(std.meta.Child(?T), T);
    try testing.expectEqual(std.meta.Child([]const T), T);
  • @typeInfo、型の詳細情報を取得することができます。
pub fn printInfo(comptime T: type) void {
    switch (@typeInfo(T)) {
        .Struct => print("Struct", .{}),
        .Optional => print("Optional", .{}),
        .Bool => print("Bool", .{}),
        .aa => {},
        else => {},
    }
    return;
}

@typeInfo の結果は、以下の場所で定義されています:

https://github.com/ziglang/zig/blob/34865d693805e1a85a42773c49049b457b087636/lib/std/builtin.zig#L228-L252


次に、フィールドのデフォルト値を取得する方法です。

@typeInfo を使用すると、構造体の情報を取得できます。その中の field 属性を使用すると、構造体内の各フィールドの情報を取得できます。StructField の定義は以下のようになります

https://github.com/ziglang/zig/blob/34865d693805e1a85a42773c49049b457b087636/lib/std/builtin.zig#L316-L322

この構造体には、デフォルト値への型消去ポインタが含まれています。*anyopaque は、C言語 のvoid ポインタのようなものです。対応する型に変換するためには、@ptrCast を使用する必要があります。

pub fn main() !void {
    const T = struct {
        a: i32 = 10,
    };

    const field = @typeInfo(T).Struct.fields[0];
    if (field.default_value) |default_value| {
        const anyopaque_pointer: *anyopaque = @constCast(default_value);
        const value = @ptrCast(*field.type, @alignCast(field.alignment, anyopaque_pointer)).*;
        std.debug.print("default value is {d}\n", .{value});
    }
}

おわりに

Zig のドキュメントは十分な内容ではありません。reddit や Stack Overflow の親切な人々が私を多く助けてくれました。本当に感謝しています。

Discussion