Open13
ちょこっとZig (Tips)
ピン留めされたアイテム
Zig Playground
(本コメント公開時点でバージョンはいずれも0.11.0)
項目 | https://zig-play.dev/ | https://codapi.org/zig/ | https://www.zigfiddle.dev/ |
---|---|---|---|
main実行 | ○ | ○ | ○ |
実行中表示 | ✕ | ○ | ✕ |
行番号 | ✕ | ○ | ○ |
ソース整形 | ○ | ✕ | ○ |
テスト実行 | ✕ | ○ | ✕ |
share | ✕ | ○ | ○ |
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});
}
匿名関数(無名関数)の取得
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)});
}
コマンドライン引数の取得
コマンドライン引数をすべて取得
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.
実行環境の情報を取得
- 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
ソート
ソート
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
型のつくり方(数値型) - @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
子プロセスでコマンド実行(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
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").? });
}
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));
}
桁数の多い計算
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
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
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"}