Open23

Zig 言語

sonicdeath9sonicdeath9

関数

  • 関数の引数はイミュータブル(値を変更できない)
  • 関数名はキャメルケース(e.g. myFunction)
sonicdeath9sonicdeath9

Defer

test "defer" {
    var x: i16 = 5;
    {
        defer x += 2;
        try expect(x == 5); // pass
    }
    try expect(x == 7); // pass
}

deferは現在のブロック({}で囲まれてる部分)を抜けるときに実行される
リソースの開放をするときとかに便利そう

sonicdeath9sonicdeath9

Error

Zigに例外はない。エラーは値である

const FielOpenError = error{
    AccessDenied,
    OutofMemory,
    FileNotFound,
};


列挙型に似ている(らしい)

const AllocationError = error{OutOfMemory};

test "error union" {
    const maybe_error: AllocationError!u16 = 10; // error set と u16 のunion型
    const no_error = maybe_error catch 0;

    try expect(@TypeOf(no_error) == u16); // pass
    try expect(no_error == 10); // pass
}


catch以降の式はその前の式がエラーのとき評価される

test "error test" {
    const maybe_error: AllocationError!u16 = AllocationError.OutOfMemory;
    const no_error = maybe_error catch (1 + 5);

    std.debug.print("\ntype of no_error = {!}\n", .{@TypeOf(no_error)});
    try expect(no_error == 6); // pass
}
type of no_error = comptime_int


エラーのマージ

const A = error{ NotDir, PathNotFound };
const B = error{ OutOfMemory, PathNotFound };
const C = A || B;

std.debug.print("\nC = {!}\n", .{C});
C = error{OutOfMemory,NotDir,PathNotFound}


これもいけた

test "big exp after catch" {
    var i: i32 = 1;
    const maybe_error: AllocationError!u16 = AllocationError.OutOfMemory;
    _ = maybe_error catch {
        while (true) {
            if (i == 1024) break;
            i *= 2;
        }
        try expect(i == 1024);
    };
}
sonicdeath9sonicdeath9

switchは文にも式にもなる

test "switch statement" {
    var x: i8 = 10;
    switch (x) {
        -1...1 => {
            x = -x;
        },
        10, 100 => {
            x = @divExact(x, 10);
        },
        else => {},
    }
    try expect(x == 1);

    x = switch (x) {
        -1...1 => -x,
        10, 100 => @divExact(x, 10),
        else => x,
    };
    try expect(x == -1);
}
sonicdeath9sonicdeath9

Runtime Safety

安全ではないコードを書ける

test "@setRuntimeSafety" {
    {
        var x: u8 = 255;
        var y: i8 = 127;

        {
            @setRuntimeSafety(false);
            x += 1;
            y += 127;
            y += 2;
            std.debug.print("\nx = {x} y = {x}\n", .{ x, y });
        }
    }
}
x = 0 y = 0
sonicdeath9sonicdeath9

ポインタ

基本的にはc言語と同じ。
参照外しはhoge.*

fn increment(num: *u8) void {
    num.* += 1;
}

test "pointers" {
    var x: u8 = 1;
    increment(&x);
    try expect(x == 2);
}

定数へのポインタは参照外しを行っても値の変更はできない

sonicdeath9sonicdeath9

配列とポインタ

fn increment(arr_ptr: [*]u8) void {
    arr_ptr[0] += 1;
}

test "pointers" {
    var y: [5]u8 = [5]u8{ 1, 2, 3, 4, 5 };
    increment(&y);
    try expect(y[0] == 2);
}
sonicdeath9sonicdeath9

Enum

const Direction = enum { north, south, east, west };
const Value = enum(u2) { zero, one, three = 3 };

test "enum type" {
    try expect(@TypeOf(Direction.north) == Direction);
}

test "enum ordinal value" {
    try expect(@intFromEnum(Value.zero) == 0);
    try expect(@intFromEnum(Value.one) == 1);
    try expect(@intFromEnum(Value.three) == 3);
}


メソッドの定義も可能

const Suit = enum {
    var count: u32 = 0;
    clubs,
    spades,
    diamonds,
    hearts,
    pub fn isClubs(self: Suit) bool {
        return self == Suit.clubs;
    }
};

test "enum method" {
    try expect(Suit.spades.isClubs() == Suit.isClubs(.spades));
    Suit.count += 1;
    try expect(Suit.count == 1);
}
sonicdeath9sonicdeath9

構造体 struct

やりたい放題

const Vec3 = struct {
    x: f32,
    y: f32,
    z: f32 = 0,
    fn swap(self: *Vec3) void {
        const tmp = self.x;
        self.x = self.y;
        self.y = tmp;
    }
};

test "struct usage" {
    var my_vector = Vec3{
        .x = 0,
        .y = 100,
    };

    my_vector.swap();
    try expect(my_vector.x == 100);
    try expect(my_vector.y == 0);
}

swap()関数を見てわかるとおり、structへのポインタは勝手に参照外しされる。

sonicdeath9sonicdeath9

union

enumはunionのどのフィールドがアクティブか調べるのに使える

const Result = union {
    int: i64,
    float: f64,
    bool: bool,
};

test "simple union" {
    var result = Result{ .int = 1234 };
    result.float = 12.34; // アクティブでないフィールドにはアクセスできない
}
const Tag = enum { a, b, c };
const Tagged = union(Tag) { a: u8, b: f32, c: bool };

test "switch on tagged union" {
    var value = Tagged{ .b = 1.5 };
    switch (value) {
        .a => |*byte| byte.* += 1,
        .b => |*float| float.* *= 2,
        .c => |*b| b.* = !b.*,
    }
    try expect(value.b == 3);
}


下と上は同じ

const NewTagged = union(enum) { a: u8, b: f32, c: bool };

test "union with enum" {
    var value = NewTagged{ .b = 1.5 };
    switch (value) {
        .a => |*byte| byte.* += 1,
        .b => |*float| float.* *= 2,
        .c => |*b| b.* = !b.*,
    }
    try expect(value.b == 3);
}
sonicdeath9sonicdeath9

ラベル付きブロック

ブロックにはラベルをつけることができ、値と同じように使える

test "labelled blocks" {
    const count = blk: {
        var sum: u32 = 0;
        var i: u32 = 0;
        while (i < 10) : (i += 1) sum += i;
        break :blk sum;
    };
    try expect(count == 45);
    try expect(@TypeOf(count) == u32);
}
sonicdeath9sonicdeath9

ラベル付きループ

ループに名前をつけることで、特定のループをcontinueもしくはbreakすることができる

test "nested continue" {
    var count: usize = 0;
    outer: for ([_]i32{ 1, 2, 3, 4, 5, 6, 7, 8 }) |_| {
        for ([_]i32{ 1, 2, 3, 4, 5 }) |_| {
            count += 1;
            continue :outer;
        }
    }
    try expect(count == 8);
}
sonicdeath9sonicdeath9

comptime

comptimeをブロックの前につけるとコンパイル時に実行されるようになるそう

test "comptime blocks" {
    const x = comptime fibonacchi(10);
    const y = comptime blk: {
        break :blk fibonacchi(10);
    };
    try expect(y == 55);
    try expect(x == 55);
}
sonicdeath9sonicdeath9

フィールド名のない構造体はタプルになる。タプルの要素は@""でアクセスできる

test "tuple" {
    const values = .{
        @as(u32, 1234),
        @as(f64, 12.34),
        true,
        "hi",
    } ++ .{false} ** 2 ++ .{.{false}};
    try expect(values[0] == 1234);
    try expect(values[4] == false);
    inline for (values, 0..) |v, i| {
        if (i != 2) continue;
        try expect(v);
    }
    try expect(values.len == 7);
    try expect(values.@"3"[0] == 'h');

    debug.print("\n{!}\n", .{values});
}
{ 1234, 1.234e1, true, { 104, 105 }, false, false, { false } }
sonicdeath9sonicdeath9

vector

vectorは配列のようにインデックスでアクセスが可能

test "vector add" {
    const x: @Vector(4, f32) = .{ 1, -10, 20, -1 };
    const y: @Vector(4, f32) = .{ 2, 10, 0, 1 };
    const z = x + y;
    try expect(z[1] == 0);
}
sonicdeath9sonicdeath9

標準ライブラリ

Allocators

標準ライブラリでは意図しないメモリのアロケーションは起こり得ない


基本的なアロケーションの方法は以下の通り。
(後述する方法より非効率)

const std = @import("std");
const expect = std.testing.expect;

test "allocation" {
   const allocator = std.heap.page_allocator;

   const memory = try allocator.alloc(u8, 100);
   defer allocator.free(memory);

   try expect(memory.len == 100);
   try expect(@TypeOf(memory) == []u8);
}


以下の方法では固定されたバッファからメモリをアロケートするため、heapからのアロケーションは起こらないという。

    test "fixed buffer allocation" {
    var buffer: [1000]u8 = undefined;
    var fba = std.heap.FixedBufferAllocator.init(&buffer);
    const allocator = fba.allocator();

    const memory = try allocator.alloc(u8, 100);
    defer allocator.free(memory);

    try expect(memory.len == 100);
    try expect(@TypeOf(memory) == []u8);
}


単独の値の場合以下のようにアロケートする。

test "allocator create/destroy" {
    const byte = try std.heap.page_allocator.create(u8);
    defer std.heap.page_allocator.destroy(byte);
    byte.* = 128;
}


general purpose allocator略してGPA
パフォーマンスより安全性を優先しており、double-freeuafを防ぐ

test "gpa" {
    var gpa = std.heap.GeneralPurposeAllocator(.{}){};
    const allocator = gpa.allocator();
    defer {
        const deinit_status = gpa.deinit();
        if (deinit_status == .leak) expect(false) catch @panic("TEST FAIL");
    }

    const bytes = try allocator.alloc(u8, 100);
    defer allocator.free(bytes);
}

パフォーマンスを重視するならstd.heap.c_allocatorも使える。コンパイル時に-lclibcとリンクすることで利用可能。

sonicdeath9sonicdeath9

ArrayList

std.ArrayLIstはc++のstd::vector<T>と似ている
使用後は.deinit()を忘れずに

const ArrayList = std.ArrayList;
const test_allocator = std.testing.allocator;

test "arraylist" {
    var list = ArrayList(u8).init(test_allocator);
    defer list.deinit();

    try list.append('H');
    try list.append('e');
    try list.append('l');
    try list.append('l');
    try list.append('o');
    try list.appendSlice(" World!");

    try expect(eql(u8, list.items, "Hello World!"));
    try expect(list.items[0] == 'H');
}
sonicdeath9sonicdeath9

FileSystem

ファイルの作成

test "createFile, write, seekTo, read" {
    const file = try std.fs.cwd().createFile(
        "jnk_file.txt",
        .{ .read = true },
    );
    defer file.close();

    const bytes_written = try file.writeAll("hello file!");
    _ = bytes_written;

    var buffer: [100]u8 = undefined;
    try file.seekTo(0);
    const bytes_read = try file.readAll(&buffer);

    std.debug.print("\n{!}\n", .{file.stat()});

    try expect(eql(u8, buffer[0..bytes_read], "hello file!"));
}
fs.File.Stat{ .inode = 23484707, .size = 11, .mode = 33188, .kind = fs.File.Kind.file, .atime = 1714909232899397231, .mtime = 1714909232899397231, .ctime = 1714909232899397231 }


ディレクトリの作成

test "create dir" {
    try std.fs.cwd().makeDir("junk-dir");
    var junk_dir = try std.fs.cwd().openDir(
        "junk-dir",
        .{},
    );
    defer {
        junk_dir.close();
        std.fs.cwd().deleteTree("junk-dir") catch unreachable;
    }

    std.debug.print("\n{!}\n", .{junk_dir.stat()});
}
fs.File.Stat{ .inode = 23484705, .size = 4096, .mode = 16877, .kind = fs.File.Kind.directory, .atime = 1714909232899397231, .mtime = 1714909232899397231, .ctime = 1714909232899397231 }
sonicdeath9sonicdeath9

Reader

readAllAlloc()は第二引数未満の大きさのストリームを全て取り込むためのメモリをアロケートしてくれる。

test "io read usage" {
    const message = "Hello World!";

    const file = try std.fs.cwd().createFile(
        "junk_file.txt",
        .{ .read = true },
    );
    defer file.close();

    try file.writeAll(message);
    try file.seekTo(0);

    const contents = try file.reader().readAllAlloc(
        test_allocator,
        message.len,
    );
    defer test_allocator.free(contents);

    try expect(eql(u8, contents, message));
}
sonicdeath9sonicdeath9

乱数 random

test "random numbers" {
    var prng = std.rand.DefaultPrng.init(blk: {
        var seed: u64 = undefined;
        try std.posix.getrandom(std.mem.asBytes(&seed));
        break :blk seed;
    });

    const rand = prng.random();

    const a = rand.float(f32);
    const b = rand.boolean();

    std.debug.print("\n{} {}\n", .{ a, b });
}
9.582089e-1 false
sonicdeath9sonicdeath9

Build

zigにはbuildというコマンドがある。
恐らく、cとかc++でいうmakeにあたるもので、ビルドの設定をbuild.zig(makefileにあたるもの?)に記述する

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

pub fn build(b: *std.Build) void {
    const exe_name = b.option(
        []const u8,
        "exe_name",
        "Name of the executable",
    ) orelse "hello";

    const exe = b.addExecutable(.{
        .name = exe_name,
        .root_source_file = .{ .path = "src/main.zig" },
        .target = b.standardTargetOptions(.{}),
        .optimize = b.standardOptimizeOption(.{}),
    });

    b.installArtifact(exe);
}
src/main.zig
const std = @import("std");

pub fn main() void {
    std.debug.print("Hello, {s}!\n", .{"Zig Build"});
}
$ zig build --summary all
Build Summary: 3/3 steps succeeded
install cached
└─ install hello cached
   └─ zig build-exe hello Debug native cached 2ms MaxRSS:37M