2
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

サクッと学ぶ Zig 言語 Beginner — 明示的制御の基礎

Last updated at Posted at 2025-11-30

目的

この記事では、Zigの基本構文を理解し、簡単なプログラムを書けるようになることを目指します。

対象読者:

  • Rust経験者(特にRustシリーズを読んだ人)
  • C/C++経験者
  • システムプログラミングに興味がある人

この記事で学ぶこと:

  • Zigの基本構文とRust/Cとの違い
  • エラーハンドリング(try/catch)
  • Optional型による安全なnull処理
  • defer/errdefer によるリソース管理
  • 構造体とメソッド
  • テストの書き方

重要: Zigは「明示的であること」を重視します。隠れた制御フローがなく、すべてが明確です。


0. Zigとは何か

Zigの設計思想

Zigは「C言語の後継」を目指すシステムプログラミング言語です。

3つの核心原則:

  1. No hidden control flow(隠れた制御フローなし)
    • 演算子オーバーロードなし
    • 例外なし
    • デストラクタなし
    • すべてが明示的
  2. Explicit allocators(明示的なアロケータ)
    • メモリ割り当てを明示
    • Rustの所有権とは異なるアプローチ
  3. Compile-time code execution(コンパイル時コード実行)
    • comptime による強力なメタプログラミング

Rust/Cとの比較

特徴 C Rust Zig
メモリ安全性 手動 所有権システム 明示的アロケータ
エラー処理 errno, NULL Result error union (!T)
null安全性 なし Option ?T
ジェネリクス マクロ トレイト comptime
ビルドシステム Make/CMake Cargo build.zig
C互換性 ネイティブ unsafe + FFI @cImport

Zigのユースケース:

  • システムプログラミング(OS、ドライバ)
  • 組み込みシステム
  • CLIツール
  • ゲームエンジン(Mach)
  • ランタイム(Bun、Tigerbeetle)
  • Cライブラリの置き換え

学習ポイント:

  • Zigは「より良いC」
  • Rustとは異なる安全性のアプローチ
  • シンプルさと制御を重視

1. Hello World と開発環境

Zigのインストール

https://ziglang.org/download/ から最新版をダウンロード

最初のプログラム

// hello.zig
const std = @import("std");

pub fn main() !void {
    const stdout = std.io.getStdOut().writer();
    try stdout.print("Hello, Zig!\n", .{});
}
# 実行
zig run hello.zig

# コンパイル
zig build-exe hello.zig
./hello

学習ポイント:

  • const std = @import("std") で標準ライブラリをインポート
  • pub fn main() がエントリーポイント
  • !void はエラーを返す可能性がある(後述)
  • try でエラーを伝播

プロジェクトの作成

新しいプロジェクト

zig init

プロジェクト構成

my-project/
├── build.zig        # ビルド設定
├── build.zig.zon    # 依存関係
└── src/
    ├── main.zig     # エントリーポイント
    └── root.zig     # ライブラリルート

ビルド

zig build

実行

zig build run

テスト

zig build test

学習ポイント:

  • zig init でプロジェクト作成
  • build.zig がビルドスクリプト(後述)
  • zig build でビルド

よくあるコンパイルエラー:

error: expected type 'void', found 'void!error{...}'
  • 原因: エラーを返す関数を try なしで呼び出し
  • 対処: try を付ける

GitHub


2. 変数宣言

var と const

const std = @import("std");

pub fn main() void {
    // const: 不変(Rustの let)
    const x: i32 = 10;
    // x = 20;  // ❌ エラー

    // var: 可変(Rustの let mut)
    var y: i32 = 10;
    y = 20;  // ✅ OK

    std.debug.print("x = {}, y = {}\n", .{x, y});
}

型推論

pub fn main() void {
    // 型推論(Rustと同様)
    const x = 10;        // i32 と推論
    const pi = 3.14;     // f64 と推論
    const flag = true;   // bool

    // 明示的な型
    const y: u8 = 255;
    const z: f32 = 1.5;
}

comptime変数(軽く)

pub fn main() void {
    // コンパイル時定数
    comptime const x = 10;
    comptime const y = x + 20;

    // コンパイル時に計算される
    const result = comptime fibonacci(10);
    std.debug.print("fibonacci(10) = {}\n", .{result});
}

fn fibonacci(n: comptime_int) comptime_int {
    if (n <= 1) return n;
    return fibonacci(n - 1) + fibonacci(n - 2);
}

Rustとの比較:

// Rust
let x = 10;           // 不変
let mut y = 10;       // 可変
const MAX: i32 = 100; // コンパイル時定数
// Zig
const x: i32 = 10;        // 不変
var y: i32 = 10;          // 可変
comptime const MAX = 100; // コンパイル時定数

学習ポイント:

  • const がデフォルト(Rustと同じ思想)
  • var は最小限に
  • comptime はコンパイル時に評価

よくあるコンパイルエラー:

error: cannot assign to constant
  • 原因: const 変数に再代入
  • 対処: var に変更

GitHub


3. 基本的な型

整数型

pub fn main() void {
    // 符号付き整数
    const i8_val: i8 = -128;
    const i16_val: i16 = -32768;
    const i32_val: i32 = -2147483648;
    const i64_val: i64 = -9223372036854775808;

    // 符号なし整数
    const u8_val: u8 = 255;
    const u16_val: u16 = 65535;
    const u32_val: u32 = 4294967295;
    const u64_val: u64 = 18446744073709551615;

    // 任意サイズの整数
    const i3_val: i3 = -4;  // -4 から 3
    const u7_val: u7 = 127; // 0 から 127

    // ポインタサイズ
    const isize_val: isize = -100;
    const usize_val: usize = 100;
}

浮動小数点型

pub fn main() void {
    const f32_val: f32 = 3.14;
    const f64_val: f64 = 3.141592653589793;

    // 型推論ではf64
    const pi = 3.14;
}

bool と文字

pub fn main() void {
    const flag: bool = true;
    const letter: u8 = 'A';  // ASCII文字

    // Zigには文字型がない(u8を使う)
}

Rustとの違い:

Rust Zig
文字 char (Unicode) u8 (ASCII)
文字列 String, &str []const u8
サイズ指定整数 i8, i16, i32 i8, i16, i32
任意サイズ なし i3, u7など

学習ポイント:

  • Zigは任意ビット幅の整数をサポート
  • 文字は単に u8
  • 型推論は保守的(安全側に倒す)

GitHub


4. 関数定義

基本的な関数

const std = @import("std");

fn add(a: i32, b: i32) i32 {
    return a + b;
}

fn greet(name: []const u8) void {
    std.debug.print("Hello, {s}!\n", .{name});
}

pub fn main() void {
    const result = add(5, 3);
    std.debug.print("5 + 3 = {}\n", .{result});

    greet("Zig");
}

複数の戻り値(構造体を返す)

const DivResult = struct {
    quotient: i32,
    remainder: i32,
};

fn divmod(a: i32, b: i32) DivResult {
    return DivResult{
        .quotient = @divTrunc(a, b),
        .remainder = @rem(a, b),
    };
}

pub fn main() void {
    const result = divmod(10, 3);
    std.debug.print("10 / 3 = {}余り{}\n", .{result.quotient, result.remainder});
}

引数の扱い

fn increment(x: *i32) void {
    x.* += 1;
}

fn process(slice: []const u8) void {
    std.debug.print("Length: {}\n", .{slice.len});
}

pub fn main() void {
    var x: i32 = 10;
    increment(&x);  // ポインタを渡す
    std.debug.print("x = {}\n", .{x});  // 11

    const data = "Hello";
    process(data);
}

Rustとの比較:

// Rust
fn add(a: i32, b: i32) -> i32 {
    a + b  // 暗黙的なreturn
}
// Zig
fn add(a: i32, b: i32) i32 {
    return a + b;  // 明示的なreturn
}

学習ポイント:

  • fn で関数定義
  • pub で公開
  • 戻り値の型は > ではなく直接指定
  • return は必須(暗黙的な戻り値なし)

よくあるコンパイルエラー:

error: expected type 'i32', found 'void'
  • 原因: 戻り値の型と実際の戻り値が不一致
  • 対処: return 文を追加または型を修正

GitHub


5. 制御構文

if(式として扱える)

pub fn main() void {
    const x = 10;

    // if 文
    if (x > 5) {
        std.debug.print("x is greater than 5\n", .{});
    } else {
        std.debug.print("x is not greater than 5\n", .{});
    }

    // if 式(Rustと同じ)
    const result = if (x > 5) "big" else "small";
    std.debug.print("x is {s}\n", .{result});

    // else if
    if (x < 0) {
        std.debug.print("negative\n", .{});
    } else if (x == 0) {
        std.debug.print("zero\n", .{});
    } else {
        std.debug.print("positive\n", .{});
    }
}

while

pub fn main() void {
    var i: i32 = 0;

    while (i < 5) {
        std.debug.print("{}\n", .{i});
        i += 1;
    }

    // continue式付き
    i = 0;
    while (i < 5) : (i += 1) {
        std.debug.print("{}\n", .{i});
    }
}

for

pub fn main() void {
    const items = [_]i32{ 1, 2, 3, 4, 5 };

    // 要素のイテレート
    for (items) |item| {
        std.debug.print("{}\n", .{item});
    }

    // インデックス付き
    for (items, 0..) |item, i| {
        std.debug.print("[{}] = {}\n", .{i, item});
    }

    // 範囲(0.10 で初期化された配列と同じ)
    for (0..5) |i| {
        std.debug.print("{}\n", .{i});
    }
}

switch(Rustのmatch相当)

pub fn main() void {
    const value = 2;

    // switch 式
    const result = switch (value) {
        1 => "one",
        2 => "two",
        3 => "three",
        else => "other",
    };

    std.debug.print("value is {s}\n", .{result});

    // 複数の値
    const category = switch (value) {
        1, 2, 3 => "small",
        4, 5, 6 => "medium",
        else => "large",
    };

    // 範囲
    const range_result = switch (value) {
        0...9 => "single digit",
        10...99 => "double digit",
        else => "large",
    };
}

Rustとの比較:

// Rust: match
let result = match value {
    1 => "one",
    2 => "two",
    _ => "other",
};
// Zig: switch
const result = switch (value) {
    1 => "one",
    2 => "two",
    else => "other",
};

学習ポイント:

  • ifswitch は式として使える
  • for はイテレータではなく配列/スライス専用
  • switch は網羅性チェックあり(Rustと同じ)
  • else で残りをキャッチ

よくあるコンパイルエラー:

error: switch must handle all possibilities
  • 原因: switch で全パターンを網羅していない
  • 対処: else を追加

GitHub


6. エラーハンドリング

エラーセット

const std = @import("std");

// エラーセットの定義
const MathError = error{
    DivisionByZero,
    Overflow,
    NegativeValue,
};

// エラーユニオン(Rustの Result<T, E> 相当)
fn divide(a: i32, b: i32) MathError!i32 {
    if (b == 0) return error.DivisionByZero;
    return @divTrunc(a, b);
}

pub fn main() void {
    const result = divide(10, 2) catch |err| {
        std.debug.print("Error: {}\n", .{err});
        return;
    };

    std.debug.print("Result: {}\n", .{result});
}

try と catch

const FileError = error{
    FileNotFound,
    PermissionDenied,
};

fn readFile(path: []const u8) FileError![]const u8 {
    if (std.mem.eql(u8, path, "missing.txt")) {
        return error.FileNotFound;
    }
    return "file contents";
}

fn processFile(path: []const u8) FileError!void {
    // try でエラーを伝播(Rustの ? 演算子)
    const contents = try readFile(path);
    std.debug.print("Contents: {s}\n", .{contents});
}

pub fn main() void {
    // catch でエラーをハンドル
    processFile("data.txt") catch |err| {
        std.debug.print("Failed to process file: {}\n", .{err});
    };

    // 特定のエラーをキャッチ
    const result = readFile("missing.txt") catch |err| switch (err) {
        error.FileNotFound => {
            std.debug.print("File not found\n", .{});
            return;
        },
        error.PermissionDenied => {
            std.debug.print("Permission denied\n", .{});
            return;
        },
    };
}

anyerror

// 任意のエラーを返せる
fn mightFail() anyerror!i32 {
    return error.SomethingWentWrong;
}

// エラーセットを推論
fn autoInferError() !i32 {
    if (false) return error.Failed;
    return 42;
}

Rustとの比較:

// Rust
fn divide(a: i32, b: i32) -> Result<i32, String> {
    if b == 0 {
        return Err("Division by zero".to_string());
    }
    Ok(a / b)
}

fn process() -> Result<i32, String> {
    let result = divide(10, 0)?;  // ? でエラー伝播
    Ok(result)
}
// Zig
fn divide(a: i32, b: i32) !i32 {
    if (b == 0) return error.DivisionByZero;
    return @divTrunc(a, b);
}

fn process() !i32 {
    const result = try divide(10, 0);  // try でエラー伝播
    return result;
}

学習ポイント:

  • error でエラー値を定義
  • !T はエラーユニオン(T または error
  • try でエラーを伝播(Rustの ?
  • catch でエラーをハンドル
  • Rustの Result より軽量

よくあるコンパイルエラー:

error: expected type 'i32', found 'error{DivisionByZero}!i32'
  • 原因: エラーユニオンを trycatch なしで使用
  • 対処: try を付けるか catch でハンドル

GitHub


7. Optional型

?T の基本

const std = @import("std");

// Optional型(Rustの Option<T> 相当)
fn find(items: []const i32, target: i32) ?usize {
    for (items, 0..) |item, i| {
        if (item == target) return i;
    }
    return null;
}

pub fn main() void {
    const numbers = [_]i32{ 10, 20, 30, 40, 50 };

    // orelse でデフォルト値
    const index1 = find(&numbers, 30) orelse 999;
    std.debug.print("Index: {}\n", .{index1});  // 2

    const index2 = find(&numbers, 99) orelse 999;
    std.debug.print("Index: {}\n", .{index2});  // 999

    // if でパターンマッチ
    if (find(&numbers, 20)) |idx| {
        std.debug.print("Found at index {}\n", .{idx});
    } else {
        std.debug.print("Not found\n", .{});
    }
}

.? による強制アンラップ

pub fn main() void {
    const maybe_value: ?i32 = 42;

    // .? で強制アンラップ(nullならpanicのような挙動)
    const value = maybe_value.?;
    std.debug.print("Value: {}\n", .{value});

    // nullの場合は実行時エラー
    // const none: ?i32 = null;
    // const bad = none.?;  // ❌ panic
}

Optional型の組み合わせ

const User = struct {
    name: []const u8,
    email: ?[]const u8,  // emailはオプショナル
};

fn getUserEmail(user: User) []const u8 {
    return user.email orelse "no-email@example.com";
}

pub fn main() void {
    const user1 = User{
        .name = "Alice",
        .email = "alice@example.com",
    };

    const user2 = User{
        .name = "Bob",
        .email = null,
    };

    std.debug.print("{s}: {s}\n", .{user1.name, getUserEmail(user1)});
    std.debug.print("{s}: {s}\n", .{user2.name, getUserEmail(user2)});
}

Rustとの比較:

// Rust
fn find(items: &[i32], target: i32) -> Option<usize> {
    items.iter().position(|&x| x == target)
}

// unwrap_or でデフォルト値
let index = find(&numbers, 30).unwrap_or(999);

// if let でパターンマッチ
if let Some(idx) = find(&numbers, 20) {
    println!("Found at {}", idx);
}
// Zig
fn find(items: []const i32, target: i32) ?usize {
    for (items, 0..) |item, i| {
        if (item == target) return i;
    }
    return null;
}

// orelse でデフォルト値
const index = find(&numbers, 30) orelse 999;

// if でパターンマッチ
if (find(&numbers, 20)) |idx| {
    std.debug.print("Found at {}\n", .{idx});
}

学習ポイント:

  • ?TT または null
  • orelse でデフォルト値を指定
  • .? で強制アンラップ(危険)
  • if でパターンマッチング

GitHub


8. 配列とスライス

配列

pub fn main() void {
    // 配列の宣言
    const array1 = [5]i32{ 1, 2, 3, 4, 5 };

    // 型推論([_] で要素数を推論)
    const array2 = [_]i32{ 10, 20, 30 };

    // 要素にアクセス
    std.debug.print("array1[0] = {}\n", .{array1[0]});

    // 長さ
    std.debug.print("Length: {}\n", .{array1.len});

    // 繰り返し初期化
    const zeros = [_]i32{0} ** 10;  // [0, 0, 0, ..., 0]
}

スライス

pub fn main() void {
    const array = [_]i32{ 1, 2, 3, 4, 5 };

    // 配列全体のスライス
    const slice1: []const i32 = &array;

    // 部分スライス
    const slice2 = array[1..4];  // [2, 3, 4]

    // 開始のみ指定
    const slice3 = array[2..];   // [3, 4, 5]

    for (slice2) |item| {
        std.debug.print("{}\n", .{item});
    }
}

文字列(スライス)

pub fn main() void {
    // 文字列リテラル([]const u8)
    const str1: []const u8 = "Hello, Zig!";

    // 型推論
    const str2 = "Hello, World!";

    // 文字列の長さ
    std.debug.print("Length: {}\n", .{str1.len});

    // 文字列の比較
    if (std.mem.eql(u8, str1, str2)) {
        std.debug.print("Equal\n", .{});
    } else {
        std.debug.print("Not equal\n", .{});
    }

    // 部分文字列
    const substr = str1[0..5];  // "Hello"
    std.debug.print("{s}\n", .{substr});
}

可変スライス

pub fn main() void {
    var array = [_]i32{ 1, 2, 3, 4, 5 };

    // 可変スライス
    const slice: []i32 = &array;

    slice[0] = 10;
    slice[1] = 20;

    for (slice) |item| {
        std.debug.print("{}\n", .{item});
    }
}

Rustとの比較:

// Rust
let array: [i32; 5] = [1, 2, 3, 4, 5];
let slice: &[i32] = &array[1..4];
let str: &str = "Hello";
// Zig
const array = [5]i32{ 1, 2, 3, 4, 5 };
const slice: []const i32 = array[1..4];
const str: []const u8 = "Hello";

学習ポイント:

  • 配列: [N]T(固定サイズ)
  • スライス: []T(可変サイズ)
  • 文字列: []const u8(バイト列)
  • Zigには String 型がない

GitHub


9. 構造体

基本的な構造体

const std = @import("std");

const Point = struct {
    x: f32,
    y: f32,

    // メソッド(実は関数)
    pub fn init(x: f32, y: f32) Point {
        return Point{ .x = x, .y = y };
    }

    pub fn distance(self: Point) f32 {
        return @sqrt(self.x * self.x + self.y * self.y);
    }

    pub fn add(self: Point, other: Point) Point {
        return Point{
            .x = self.x + other.x,
            .y = self.y + other.y,
        };
    }
};

pub fn main() void {
    const p1 = Point.init(3.0, 4.0);
    const p2 = Point{ .x = 1.0, .y = 2.0 };

    std.debug.print("Distance: {}\n", .{p1.distance()});

    const p3 = p1.add(p2);
    std.debug.print("Sum: ({}, {})\n", .{p3.x, p3.y});
}

デフォルト値

const Config = struct {
    host: []const u8 = "localhost",
    port: u16 = 8080,
    timeout: u32 = 30,
};

pub fn main() void {
    // デフォルト値を使う
    const config1 = Config{};

    // 一部だけ指定
    const config2 = Config{
        .port = 3000,
    };

    std.debug.print("{}:{}\n", .{config1.host, config1.port});
    std.debug.print("{}:{}\n", .{config2.host, config2.port});
}

ネストした構造体

const Address = struct {
    street: []const u8,
    city: []const u8,
};

const Person = struct {
    name: []const u8,
    age: u32,
    address: Address,
};

pub fn main() void {
    const person = Person{
        .name = "Alice",
        .age = 30,
        .address = Address{
            .street = "123 Main St",
            .city = "Springfield",
        },
    };

    std.debug.print("{s} lives in {s}\n", .{person.name, person.address.city});
}

Rustとの比較:

// Rust
struct Point {
    x: f32,
    y: f32,
}

impl Point {
    fn new(x: f32, y: f32) -> Self {
        Point { x, y }
    }

    fn distance(&self) -> f32 {
        (self.x * self.x + self.y * self.y).sqrt()
    }
}
// Zig
const Point = struct {
    x: f32,
    y: f32,

    pub fn init(x: f32, y: f32) Point {
        return Point{ .x = x, .y = y };
    }

    pub fn distance(self: Point) f32 {
        return @sqrt(self.x * self.x + self.y * self.y);
    }
};

学習ポイント:

  • struct で構造体定義
  • メソッドは構造体内の関数
  • self は明示的な引数
  • デフォルト値をサポート

GitHub


10. defer と errdefer

defer の基本

const std = @import("std");

pub fn main() !void {
    std.debug.print("Start\n", .{});

    defer std.debug.print("End\n", .{});

    std.debug.print("Middle\n", .{});

    // 出力順序:
    // Start
    // Middle
    // End
}

リソース管理

const std = @import("std");

fn processFile(allocator: std.mem.Allocator, path: []const u8) !void {
    // メモリ確保
    const buffer = try allocator.alloc(u8, 1024);
    defer allocator.free(buffer);  // スコープ終了時に解放

    std.debug.print("Processing {s}\n", .{path});

    // エラーが起きても defer は実行される
    if (std.mem.eql(u8, path, "bad.txt")) {
        return error.BadFile;
    }

    // 正常終了時も defer は実行される
}

pub fn main() !void {
    const allocator = std.heap.page_allocator;

    processFile(allocator, "good.txt") catch |err| {
        std.debug.print("Error: {}\n", .{err});
    };
}

errdefer(エラー時のみ実行)

const std = @import("std");

fn allocateAndProcess(allocator: std.mem.Allocator) ![]u8 {
    const buffer = try allocator.alloc(u8, 1024);
    errdefer allocator.free(buffer);  // エラー時のみ解放

    // 何か処理
    if (false) return error.ProcessingFailed;

    // 成功時は buffer を返す(解放しない)
    return buffer;
}

pub fn main() !void {
    const allocator = std.heap.page_allocator;

    const buffer = try allocateAndProcess(allocator);
    defer allocator.free(buffer);

    std.debug.print("Success\n", .{});
}

複数の defer

pub fn main() void {
    defer std.debug.print("1\n", .{});
    defer std.debug.print("2\n", .{});
    defer std.debug.print("3\n", .{});

    std.debug.print("Start\n", .{});

    // 出力順序(LIFO):
    // Start
    // 3
    // 2
    // 1
}

Rustとの比較:

// Rust: Drop トレイト(暗黙的)
{
    let _file = File::open("file.txt")?;
    // スコープ終了時に自動でクローズ
}
// Zig: defer(明示的)
{
    const file = try std.fs.cwd().openFile("file.txt", .{});
    defer file.close();  // 明示的に遅延実行
}

学習ポイント:

  • defer はスコープ終了時に実行
  • errdefer はエラー時のみ実行
  • LIFO順(後入れ先出し)
  • Rustの Drop より明示的

GitHub


11. comptime入門

comptime変数

pub fn main() void {
    // コンパイル時に計算
    comptime var x = 0;
    inline for (0..5) |i| {
        x += i;
    }

    std.debug.print("Sum: {}\n", .{x});  // 10
}

ジェネリック関数

fn max(comptime T: type, a: T, b: T) T {
    return if (a > b) a else b;
}

pub fn main() void {
    const x = max(i32, 10, 20);
    const y = max(f32, 1.5, 2.5);

    std.debug.print("max(10, 20) = {}\n", .{x});
    std.debug.print("max(1.5, 2.5) = {}\n", .{y});
}

学習ポイント:

  • comptime でコンパイル時計算
  • ジェネリクスは comptime で実現
  • 詳細はProfessional編で

GitHub


12. 組み込み関数(基礎)

@as(型変換)

pub fn main() void {
    const x: i32 = 10;
    const y = @as(f32, @floatFromInt(x));

    std.debug.print("x = {}, y = {}\n", .{x, y});
}

@intCast(整数キャスト)

pub fn main() void {
    const x: i32 = 1000;
    const y: i16 = @intCast(x);

    std.debug.print("y = {}\n", .{y});
}

@TypeOf(型取得)

pub fn main() void {
    const x = 10;
    const y = 3.14;

    std.debug.print("Type of x: {}\n", .{@TypeOf(x)});
    std.debug.print("Type of y: {}\n", .{@TypeOf(y)});
}

学習ポイント:

GitHub


13. テスト

test ブロック

const std = @import("std");

fn add(a: i32, b: i32) i32 {
    return a + b;
}

test "basic addition" {
    const result = add(2, 3);
    try std.testing.expectEqual(5, result);
}

test "negative numbers" {
    const result = add(-5, 3);
    try std.testing.expectEqual(-2, result);
}

// zig test でテスト実行

エラーのテスト

const MathError = error{DivisionByZero};

fn divide(a: i32, b: i32) MathError!i32 {
    if (b == 0) return error.DivisionByZero;
    return @divTrunc(a, b);
}

test "division by zero" {
    const result = divide(10, 0);
    try std.testing.expectError(error.DivisionByZero, result);
}

test "normal division" {
    const result = try divide(10, 2);
    try std.testing.expectEqual(5, result);
}

テストの実行

# 単一ファイルのテスト
zig test main.zig

# プロジェクト全体のテスト
zig build test

Rustとの比較:

// Rust
#[test]
fn test_add() {
    assert_eq!(add(2, 3), 5);
}
// Zig
test "add" {
    try std.testing.expectEqual(5, add(2, 3));
}

学習ポイント:

  • test ブロックでテスト
  • std.testing でアサーション
  • zig test で実行

GitHub


14. まとめ

Beginner編で学んだこと

Zigの基礎

  • 変数宣言(var, const, comptime)
  • 基本的な型
  • 関数定義
  • 制御構文

エラーハンドリング

  • エラーユニオン(!T)
  • try と catch
  • Rustの Result との比較

Optional型

  • ?T による null 安全性
  • orelse と .?
  • Rustの Option との比較

リソース管理

  • defer と errdefer
  • 明示的なクリーンアップ

構造体とテスト

  • struct の定義
  • メソッド
  • test ブロック

Rust vs Zig の主要な違い

概念 Rust Zig
哲学 所有権システム 明示的制御
メモリ管理 暗黙的(Drop) 明示的(defer)
エラー Result !T
null Option ?T
ジェネリクス トレイト comptime
メタプログラミング マクロ comptime
隠れた制御フロー 少ない なし

Zigの設計原則(再確認)

1. No hidden control flow

// Zigでは全てが明示的
const x = try divide(10, 0);  // エラーが明示的
defer allocator.free(buffer);  // 解放が明示的

2. Explicit allocators

// アロケータを常に渡す
const buffer = try allocator.alloc(u8, 1024);

3. Compile-time code execution

// comptimeで強力なメタプログラミング
comptime const size = calculateSize();

簡単なCLIツールの例

const std = @import("std");

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

    try stdout.print("Enter your name: ", .{});

    var buffer: [100]u8 = undefined;
    const input = try stdin.readUntilDelimiterOrEof(&buffer, '\n');

    if (input) |name| {
        const trimmed = std.mem.trim(u8, name, &std.ascii.whitespace);
        try stdout.print("Hello, {s}!\n", .{trimmed});
    }
}

次のステップ: Intermediate編へ

Beginner編を修了したあなたは:

  • ✅ Zigの基本構文を理解している
  • ✅ エラーハンドリングを使える
  • ✅ 簡単なプログラムを書ける

Intermediate編では:

  • 🚀 エラー処理の深掘り(エラーセットの組み合わせ、カスタムエラー)
  • 🚀 メモリアロケータの概念と使い方
  • 🚀 comptime の実践的な使い方
  • 🚀 ユニオンとタグ付きユニオン
  • 🚀 パッケージ構成とモジュール
  • 🚀 標準ライブラリの活用

公式リソース:

2
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
2
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?