Open13

ちょこっとZig (Tips)

PG_WalkerPG_Walker

comptimeの使いどころn選

// $ zig version
// 0.12.0-dev.3435+091aa54a3

const std = @import("std");

// typeの引数
fn add(comptime T: type, a: T, b: T) T {
    return a + b;
}

pub fn main() !void {
    const stdout = std.io.getStdOut().writer();

    // 変数の代入
    comptime var a: u8 = undefined;
    // assert
    a = 1;
    comptime std.debug.assert(@TypeOf(a) == u8);
    comptime std.debug.assert(a == 1);

    // 定数の代入
    const b = comptime a + 1;

    // 書式文字列はcomptime
    try stdout.print("a + b = {}\n", .{a + b}); // a + b = 3

    // ブロック
    comptime {
        var c: u8 = undefined;
        c = add(u8, a, b);
        // try stdout.print("a + b = {}\n", .{c}); // エラー
    }

    // struct内の変数
    const t = struct {
        comptime d: u8 = 1,
    };
    const ts = t{};
    try stdout.print("ts.d = {}\n", .{ts.d}); // ts.d = 1

    // OSごとの設定
    const builtin = @import("builtin");
    const os_name: ?[]const u8 = comptime switch (builtin.os.tag) {
        .linux => "Linux",
        .windows => "Windows",
        .macos => "MacOS",
        else => null,
    };
    try stdout.print("{?s}\n", .{os_name});
}
PG_WalkerPG_Walker

匿名関数(無名関数)の取得

const std = @import("std");

pub fn main() !void {
    const stdout = std.io.getStdOut().writer();

    // structから関数のみを取得 - 匿名関数(無名関数)
    const anon_fn = struct {
        pub fn f(a: u8, b: []const i16) f16 {
            _ = .{ a, b };
            return 1.0;
        }
    }.f;

    _ = anon_fn(1, &[_]i16{1}); // エラー防止

    // anon_fnの型 - fn (u8, []const i16) f16
    try stdout.print("{}\n", .{@TypeOf(anon_fn)});
}
PG_WalkerPG_Walker

コマンドライン引数の取得

コマンドライン引数をすべて取得
const std = @import("std");
pub fn main() !void {
    const stdout = std.io.getStdOut().writer();
    const alloc = std.heap.page_allocator;

    // コマンドライン引数をすべて取得(メモリはAllocatorで確保)
    const args = try std.process.argsAlloc(alloc);
    // 最後にメモリを解放
    defer std.process.argsFree(alloc, args);

    try stdout.print("args\n-----\n", .{});
    for (args) |arg| {
        try stdout.print("{s}\n", .{arg});
    }
}
コマンドライン引数をイテレータで1つずつ取得
const std = @import("std");
pub fn main() !void {
    const stdout = std.io.getStdOut().writer();
    const alloc = std.heap.page_allocator;

    // コマンドライン引数を取得するイテレータ
    var itor = try std.process.argsWithAllocator(alloc);
    // 最後にイテレータを解放
    defer itor.deinit();

    try stdout.print("args\n-----\n", .{});

    // コマンドライン引数を1つずつ取得
    while (itor.next()) |arg| {
        try stdout.print("{s}\n", .{arg});
    }
}
コンパイルしてから実行
$ zig build-exe -O ReleaseSafe example_args.zig
$ ./example_args Hello Zig.
args
-----
./example_args
Hello
Zig.
ソースコードから実行
$ zig run example_args.zig -- Hello Zig.
args
-----
/path/to/.../example_args
Hello
Zig.
PG_WalkerPG_Walker

実行環境の情報を取得

  • Zigのバージョン、CPUアーキテクチャ、OS、ABI
実行環境の情報を取得
const std = @import("std");
const builtin = @import("builtin");

pub fn main() !void {
    const stdout = std.io.getStdOut().writer();

    try stdout.print("zig version: {s}\n", .{builtin.zig_version_string});
    try stdout.print("target cpu:  {s}\n", .{enumTagName(builtin.cpu.arch)});
    try stdout.print("target os:   {s}\n", .{enumTagName(builtin.os.tag)});
    try stdout.print("target abi:  {s}\n", .{enumTagName(builtin.abi)});
}

// enumのタグ名を文字列に変換
fn enumTagName(e: anytype) []const u8 {
    return std.enums.tagName(@TypeOf(e), e).?;
}
結果(実行環境により異なります)
zig version: 0.12.0-dev.3496+a2df84d0f
target cpu:  x86_64
target os:   linux
target abi:  gnu
PG_WalkerPG_Walker

ソート

ソート
const std = @import("std");

pub fn main() !void {
    const stdout = std.io.getStdOut().writer();

    var a = [_]u8{ 3, 5, 2 };
    try stdout.print("a[0] = {}, a[1] = {}, a[2] = {}\n", .{ a[0], a[1], a[2] });

    // ソート用に変換
    const b: []u8 = &a;
    // 昇順にソート
    std.mem.sort(u8, b, {}, std.sort.asc(u8));
    try stdout.print("sort asc:  b[0] = {}, b[1] = {}, b[2] = {}\n", .{ b[0], b[1], b[2] });
    // 降順にソート
    std.mem.sort(u8, b, {}, std.sort.desc(u8));
    try stdout.print("sort desc: b[0] = {}, b[1] = {}, b[2] = {}\n", .{ b[0], b[1], b[2] });
}

// 結果
a[0] = 3, a[1] = 5, a[2] = 2
sort asc:  b[0] = 2, b[1] = 3, b[2] = 5
sort desc: b[0] = 5, b[1] = 3, b[2] = 2
PG_WalkerPG_Walker

型のつくり方(数値型) - @typeInfo, std.meta

const std = @import("std");

pub fn main() !void {
    const stdout = std.io.getStdOut().writer();

    // 整数型(符号あり、符号なし)
    // std.builtin.Signednessはsignedが符号あり、unsignedが符号なし
    // std.meta.Int(Signedness, ビット数) で型をつくれる
    inline for (@typeInfo(std.builtin.Signedness).Enum.fields) |field| {
        try stdout.print("{s}\n", .{field.name});
        inline for (3..10) |n| {
            // 符号あり、符号なしの設定
            const signedness: std.builtin.Signedness = @enumFromInt(field.value);
            // 整数型をつくる
            const T = std.meta.Int(signedness, 1 << n);
            try stdout.print("{} ", .{T});
        }
        try stdout.print("\n", .{});
    }

    // 実数型
    // std.meta.Float(ビット数) で型を作れる
    // 対応するビット数は限られる
    try stdout.print("float\n", .{});
    inline for ([_]u8{ 16, 32, 64, 80, 128 }) |n| {
        // 実数型をつくる
        const T = std.meta.Float(n);
        try stdout.print("{} ", .{T});
    }
    try stdout.print("\n", .{});
}

// 結果
signed
i8 i16 i32 i64 i128 i256 i512
unsigned
u8 u16 u32 u64 u128 u256 u512
float
f16 f32 f64 f80 f128
PG_WalkerPG_Walker

子プロセスでコマンド実行(std.ChlidProcess)

// zig version: 0.11.0
const std = @import("std");

pub fn main() !void {
    const stdout = std.io.getStdOut().writer();

    // 実行するコマンド
    const result = try std.ChildProcess.exec(.{
        .argv = &[_][]const u8{ "echo", "-n", "Hello Zig!" },
        .allocator = std.heap.page_allocator
    });
    // 標準出力
    try stdout.print("{s}\n", .{ result.stdout });
    // 終了ステータス
    try stdout.print("$? = {}\n", .{ result.term.Exited });
}

// 結果
Hello Zig!
$? = 0
PG_WalkerPG_Walker

OS情報の取得(Linux)

/etc/os-releaseの内容を読み込み、一部を抜粋して出力

  • std.heap.GeneralPurposeAllocator
  • std.fs.openFileAbsolute
  • std.fs.File.readToEndAlloc
  • std.math.maxInt
  • std.hash_map.StringHashMap([]const u8)
  • std.mem.TokenIterator
  • std.mem.indexOf
/etc/os-releaseの内容(環境により異なる)
$ cat /etc/os-release
NAME="Fedora Linux"
VERSION="39 (Thirty Nine)"
ID=fedora
VERSION_ID=39
.....
.....
/etc/os-releaseの一部を抜粋
const std = @import("std");

pub fn main() !void {
    const stdout = std.io.getStdOut().writer();

    // メモリアロケータでリーク検出
    // https://ziglang.org/learn/samples/#memory-leak-detection
    var gpa = std.heap.GeneralPurposeAllocator(.{}){};
    defer std.debug.assert(gpa.deinit() == .ok);
    const allocator = gpa.allocator();

    // OS情報の読み取り
    const file_name = "/etc/os-release";
    const file = try std.fs.openFileAbsolute(file_name, .{ .mode = .read_only });
    const content = try file.readToEndAlloc(allocator, std.math.maxInt(usize));
    defer allocator.free(content);
    defer file.close();

    // 情報格納用
    const string_map = std.hash_map.StringHashMap([]const u8);  // Generics
    var info_map = string_map.init(allocator);
    defer info_map.deinit();

    // 1行ずつ読み込み
    var iterator = std.mem.tokenize(u8, content, "\n");
    while (iterator.next()) |line| {
        // "="の前後で名称と値に分ける
        if (std.mem.indexOf(u8, line, "=")) |pos| {
            const name = line[0..pos];      // 名称
            const value = line[pos + 1..];  // 値
            try info_map.put(name, value);  // データを格納
            // try stdout.print("{s}: {s}\n", .{ name, value });
        }
    }

    // 取得した情報を参照 (fedora 39など)
    try stdout.print("{s} {s}\n", .{ info_map.get("ID").?, info_map.get("VERSION_ID").? });
}
PG_WalkerPG_Walker

gzipでの圧縮、解凍(展開)

//
// gzipでの圧縮、解凍(展開)
// zig test (ソースファイル名)
//
const std = @import("std");

const assert = std.debug.assert;
const allocator = std.testing.allocator;

// gzip圧縮
test "compress gzip" {
    // 原本ファイル
    const image_file = try std.fs.cwd().openFile("image_original.jpg", .{});
    // 圧縮ファイル(作成)
    const gzip_file = try std.fs.cwd().createFile("image_original.gz", .{});
    defer {
        image_file.close();
        gzip_file.close();
    }

    // gzipで圧縮
    try std.compress.gzip.compress(image_file.reader(), gzip_file.writer(), .{});
}

// gzip解凍(展開)
test "decompress gzip" {
    // gzip圧縮ファイル
    const gzip_file = try std.fs.cwd().openFile("image_original.gz", .{});
    // 解凍後ファイル(作成)
    const image_file = try std.fs.cwd().createFile("image_decompress.jpg", .{});
    defer {
        gzip_file.close();
        image_file.close();
    }

    // gzipで解凍
    try std.compress.gzip.decompress(gzip_file.reader(), image_file.writer());
}

// 原本ファイルと解凍後ファイルの比較
test "file equality" {
    // 原本ファイル
    const file_orig = try std.fs.cwd().openFile("image_original.jpg", .{});
    // 解凍後ファイル
    const file_deco = try std.fs.cwd().openFile("image_decompress.jpg", .{});
    defer {
        file_orig.close();
        file_deco.close();
    }

    // 両ファイルを読み込み
    const data_orig = try file_orig.readToEndAlloc(allocator, std.math.maxInt(usize));
    const data_deco = try file_deco.readToEndAlloc(allocator, std.math.maxInt(usize));
    defer {
        allocator.free(data_orig);
        allocator.free(data_deco);
    }

    // 両ファイルを比較
    assert(std.mem.eql(u8, data_orig, data_deco));
}
PG_WalkerPG_Walker

桁数の多い計算

Zigでは桁数の多い整数の計算もできます。このときの数値型はcomptime_intです。

fn fact(n: anytype) @TypeOf(n) {
    if (n < 1) return 1;
    return n * fact(n - 1);
}

pub fn main() !void {
    const std = @import("std");
    const stdout = std.io.getStdOut().writer();

    const n = 300;  // nはcomptime_int型
    try stdout.print("fact({}) = {}\n", .{ n, fact(n) });   // fact(n)もcomptime_int型
}
結果
fact(300) = 306057512216440636035370461297268629388588804173576999416776741259476533176716867465515291422477573349939147888701726368864263907759003154226842927906974559841225476930271954604008012215776252176854255965356903506788725264321896264299365204576448830388909753943489625436053225980776521270822437639449120128678675368305712293681943649956460498166450227716500185176546469340112226034729724066333258583506870150169794168850353752137554910289126407157154830282284937952636580145235233156936482233436799254594095276820608062232812387383880817049600000000000000000000000000000000000000000000000000000000000000000000000000
PG_WalkerPG_Walker

ZigでHTTPサーバ(std.http.Server, std.net.Address) - 0.12.0対応

httpserver_example.zig
const std = @import("std");

// URI判定用(^.....([\?/]|$))
fn eqlUriAlloc(allocator: std.mem.Allocator, uri: []const u8, target: []const u8) !bool {
    if (std.mem.eql(u8, uri, target)) return true;
    if (uri.len >= target.len) return false;

    const uri_s: []u8 = try allocator.alloc(u8, uri.len + 1);
    defer allocator.free(uri_s);
    std.mem.copyForwards(u8, uri_s, uri);

    for ([_]u8{ '?', '/' }) |c| { // '#'は自動で区切られるため不要
        uri_s[uri.len] = c;
        if (std.mem.startsWith(u8, target, uri_s)) return true;
    }
    return false;
}

pub fn main() !void {
    const stdout = std.io.getStdOut().writer();

    var gpa = std.heap.GeneralPurposeAllocator(.{}){};
    defer std.debug.assert(gpa.deinit() == .ok);
    const allocator = gpa.allocator();

    // サーバのアドレスとポート番号
    const port = 8088;
    const addr = try std.net.Address.parseIp4("127.0.0.1", port);

    // 受信バッファ
    var buff: [1024]u8 = undefined;

    // ネットワーク起動
    var net_server = try addr.listen(.{});
    try stdout.print("Server started ({}). Stop Ctrl + C...\n", .{port});

    accept: while (true) {
        const conn = try net_server.accept();
        defer conn.stream.close();

        // HTTPサーバ
        var http_server = std.http.Server.init(conn, &buff);
        while (http_server.state == .ready) {
            // リクエスト受信
            var request = http_server.receiveHead() catch |err| switch (err) {
                error.HttpConnectionClosing => continue :accept,
                else => return,
            };

            const method = request.head.method; // HTTPメソッド
            const target = request.head.target; // URI(パス)

            if (std.mem.eql(u8, target, "/")) {
                // レスポンス出力
                try request.respond("Hello Zig!", .{ .extra_headers = &.{
                    .{ .name = "content-type", .value = "text/plain;charset=UTF-8" },
                } });
                continue;
            }

            if (method == .POST and std.mem.eql(u8, target, "/echo")) {
                // リクエストボディ読み込み
                var reader = try request.reader();
                const body = try reader.readAllAlloc(allocator, 8192);
                defer allocator.free(body);

                // レスポンス出力(リクエストボディをそのまま)
                try request.respond(body, .{ .extra_headers = &.{
                    .{ .name = "content-type", .value = request.head.content_type.? },
                } });
                continue;
            }

            if (try eqlUriAlloc(allocator, "/html/hello", target)) {
                // HTMLの出力(ソースコードはUTF-8)
                try request.respond("<html><body>こんにちはZig</body></html>", .{ .extra_headers = &.{
                    .{ .name = "content-type", .value = "text/html;charset=UTF-8" },
                } });
                continue;
            }

            if (try eqlUriAlloc(allocator, "/json/hello", target)) {
                // JSONの出力(ソースコードはUTF-8)
                try request.respond("{\"message\":\"こんにちはZig\"}", .{ .extra_headers = &.{
                    .{ .name = "content-type", .value = "application/json;charset=UTF-8" },
                } });
                continue;
            }

            if (try eqlUriAlloc(allocator, "/redirect/hello", target)) {
                // /html/helloに転送
                try request.respond("", .{ .status = .found, .extra_headers = &.{
                    .{ .name = "location", .value = "/html/hello" },
                } });
                continue;
            }

            try request.respond("", .{ .status = .not_found });
        }
    }
}
結果
$ curl http://127.0.0.1:8088/
Hello Zig!

$ curl http://127.0.0.1:8088/html/hello
<html><body>こんにちはZig</body></html>

$ curl http://127.0.0.1:8088/json/hello/
{"message":"こんにちはZig"}

$ curl http://127.0.0.1:8088/html/hello?
<html><body>こんにちはZig</body></html>

$ curl http://127.0.0.1:8088/json/hello#
{"message":"こんにちはZig"}

$ curl -v -H 'Content-Type: application/json;charset=UTF-8' -d '{"message":"こんにちはZig"}' http://127.0.0.1:8088/echo
* processing: http://127.0.0.1:8088/echo
*   Trying 127.0.0.1:8088...
* Connected to 127.0.0.1 (127.0.0.1) port 8088
> POST /echo HTTP/1.1
> Host: 127.0.0.1:8088
> User-Agent: curl/8.2.1
> Accept: */*
> Content-Type: application/json;charset=UTF-8
> Content-Length: 32
>
< HTTP/1.1 200 OK
< content-length: 32
< content-type: application/json;charset=UTF-8
<
* Connection #0 to host 127.0.0.1 left intact
{"message":"こんにちはZig"}

$ curl -vL http://127.0.0.1:8088/redirect/hello
* processing: http://127.0.0.1:8088/redirect/hello
*   Trying 127.0.0.1:8088...
* Connected to 127.0.0.1 (127.0.0.1) port 8088
> GET /redirect/hello HTTP/1.1
> Host: 127.0.0.1:8088
> User-Agent: curl/8.2.1
> Accept: */*
>
< HTTP/1.1 302 Found
< content-length: 0
< location: /html/hello
<
* Connection #0 to host 127.0.0.1 left intact
* Issue another request to this URL: 'http://127.0.0.1:8088/html/hello'
* Found bundle for host: 0xa00016c20 [serially]
* Can not multiplex, even if we wanted to
* Re-using existing connection with host 127.0.0.1
> GET /html/hello HTTP/1.1
> Host: 127.0.0.1:8088
> User-Agent: curl/8.2.1
> Accept: */*
>
< HTTP/1.1 200 OK
< content-length: 44
< content-type: text/html;charset=UTF-8
<
* Connection #0 to host 127.0.0.1 left intact
<html><body>こんにちはZig</body></html>

$ curl -I http://127.0.0.1:8088/html/hellogreet
HTTP/1.1 404 Not Found
content-length: 0

参考: https://github.com/ziglang/zig/blob/master/lib/std/http/test.zig

PG_WalkerPG_Walker

ZigでHTTPクライアント(std.http.Client, std.net.Address) - 0.12.0対応

httpclient_example.zig
const std = @import("std");

pub fn main() !void {
    const stdout = std.io.getStdOut().writer();

    var gpa = std.heap.GeneralPurposeAllocator(.{}){};
    defer std.debug.assert(gpa.deinit() == .ok);
    const allocator = gpa.allocator();

    const location = "http://127.0.0.1:8088/echo";
    const uri = try std.Uri.parse(location);

    const content = "{\"message\":\"こんにちはZig\"}";

    // バッファ
    var server_header_buffer: [1024]u8 = undefined;

    // HTTPクライアント
    var client = std.http.Client{ .allocator = allocator };
    defer client.deinit();

    // 接続開始
    var req = try client.open(.POST, uri, .{ .server_header_buffer = &server_header_buffer, .extra_headers = &.{
        .{ .name = "content-type", .value = "application/json;charset=UTF-8" },
    } });
    defer req.deinit();
    req.transfer_encoding = .{ .content_length = content.len };

    // リクエスト送信
    try req.send();
    try req.writeAll(content);
    try req.finish();
    try req.wait();

    // ヘッダ用のHashMap
    const shm = std.hash_map.StringHashMap([]const u8);
    var hash_map = shm.init(allocator);
    defer hash_map.deinit();

    // ヘッダをHashMapに追加
    var it_header = req.response.iterateHeaders();
    while (it_header.next()) |header| {
        try hash_map.put(header.name, header.value);
    }

    // ヘッダの出力
    try stdout.print("Header:\n", .{});
    var it_key = hash_map.keyIterator();
    while (it_key.next()) |key| {
        try stdout.print("{s} = {s}\n", .{ key.*, hash_map.get(key.*).? });
    }

    // ボディの読み込み
    const body = try req.reader().readAllAlloc(allocator, std.math.maxInt(usize));
    defer allocator.free(body);

    // ボディの出力
    try stdout.print("Body:\n", .{});
    try stdout.print("{s}\n", .{body});
}
結果
$ zig run httpserver_example.zig &
Server started (8088). Stop Ctrl + C...

$ zig run httpclient_example.zig
Header:
content-length = 32
content-type = application/json;charset=UTF-8
Body:
{"message":"こんにちはZig"}