0
1

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 言語 Intermediate — メモリとメタプログラミング

Last updated at Posted at 2025-12-02

目的

この記事では、Zigのデザイン原則(明示的アロケータ、comptime)を理解し、実践的な設計ができるようになることを目指します。

前提知識:

  • Beginner編の内容(基本構文、エラー処理の基礎、構造体、defer/errdefer)

この記事で学ぶこと:

  • エラー処理の高度なテクニック
  • メモリアロケータの概念と使い分け
  • comptime による強力なメタプログラミング
  • タグ付きユニオン(Rustのenum相当)
  • ポインタとスライスの深い理解
  • 動的データ構造(ArrayList、HashMap)
  • パッケージとモジュールの構成

1. エラー処理の深掘り

エラーセットの組み合わせ

const std = @import("std");

const FileError = error{
    NotFound,
    PermissionDenied,
    AlreadyExists,
};

const NetworkError = error{
    ConnectionRefused,
    Timeout,
    InvalidResponse,
};

// エラーセットを結合(|| 演算子)
const AppError = FileError || NetworkError;

fn readFromNetwork(url: []const u8) AppError![]const u8 {
    if (std.mem.eql(u8, url, "bad")) {
        return error.ConnectionRefused;
    }
    if (std.mem.eql(u8, url, "missing")) {
        return error.NotFound;
    }
    return "data";
}

pub fn main() !void {
    const result = readFromNetwork("good") catch |err| {
        switch (err) {
            error.NotFound => std.debug.print("File not found\n", .{}),
            error.ConnectionRefused => std.debug.print("Connection refused\n", .{}),
            error.Timeout => std.debug.print("Timeout\n", .{}),
            else => std.debug.print("Other error: {}\n", .{err}),
        }
        return;
    };

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

エラー推論(anyerror

// anyerror は全てのエラーを含む
fn mightFail() anyerror!i32 {
    return error.UnknownError;
}

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

// 推論されたエラーセット: error{Failed, AnotherError}

カスタムエラーの設計

const std = @import("std");

// レイヤーごとにエラーを定義
const DatabaseError = error{
    ConnectionFailed,
    QueryFailed,
    TransactionFailed,
};

const ValidationError = error{
    InvalidEmail,
    InvalidPassword,
    UsernameTooShort,
};

const AuthError = DatabaseError || ValidationError;

const User = struct {
    id: u32,
    username: []const u8,
    email: []const u8,
};

fn validateUsername(username: []const u8) ValidationError!void {
    if (username.len < 3) {
        return error.UsernameTooShort;
    }
}

fn validateEmail(email: []const u8) ValidationError!void {
    if (std.mem.indexOf(u8, email, "@") == null) {
        return error.InvalidEmail;
    }
}

fn createUser(username: []const u8, email: []const u8) AuthError!User {
    // バリデーション(エラーは自動で伝播)
    try validateUsername(username);
    try validateEmail(email);

    // データベース操作をシミュレート
    if (std.mem.eql(u8, username, "error")) {
        return error.ConnectionFailed;
    }

    return User{
        .id = 1,
        .username = username,
        .email = email,
    };
}

pub fn main() !void {
    const user = createUser("alice", "alice@example.com") catch |err| {
        switch (err) {
            error.UsernameTooShort => {
                std.debug.print("Username must be at least 3 characters\n", .{});
            },
            error.InvalidEmail => {
                std.debug.print("Invalid email format\n", .{});
            },
            error.ConnectionFailed => {
                std.debug.print("Database connection failed\n", .{});
            },
            else => {
                std.debug.print("Unknown error: {}\n", .{err});
            },
        }
        return;
    };

    std.debug.print("Created user: {s}\n", .{user.username});
}

errdefer の実践パターン

const std = @import("std");

fn processWithCleanup(allocator: std.mem.Allocator) !void {
    // リソース1を確保
    const buffer1 = try allocator.alloc(u8, 1024);
    errdefer allocator.free(buffer1);  // エラー時にクリーンアップ

    // リソース2を確保
    const buffer2 = try allocator.alloc(u8, 2048);
    errdefer allocator.free(buffer2);  // エラー時にクリーンアップ

    // エラーが起きるかもしれない処理
    if (false) return error.ProcessingFailed;

    // 成功時はdeferで解放
    defer {
        allocator.free(buffer1);
        allocator.free(buffer2);
    }

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

Rustとの比較:

// Rust: Result<T, E> と thiserror
#[derive(Error, Debug)]
enum AppError {
    #[error("File error: {0}")]
    File(#[from] std::io::Error),
    #[error("Network error: {0}")]
    Network(String),
}
// Zig: エラーセットの組み合わせ
const FileError = error{NotFound, PermissionDenied};
const NetworkError = error{ConnectionRefused, Timeout};
const AppError = FileError || NetworkError;

学習ポイント:

  • || でエラーセットを結合
  • anyerror は全エラーを含む
  • エラーは型として推論される
  • レイヤーごとにエラーを定義
  • errdefer でリソース管理

GitHub


2. メモリアロケータの概念

なぜアロケータが必要か

Zigには以下がありません:

  • ガーベージコレクタ
  • 暗黙的なメモリ管理
  • グローバルアロケータ

代わりに:明示的にアロケータを渡す

const std = @import("std");

pub fn main() !void {
    // アロケータを明示的に選択
    var gpa = std.heap.GeneralPurposeAllocator(.{}){};
    defer _ = gpa.deinit();

    const allocator = gpa.allocator();

    // メモリ確保
    const buffer = try allocator.alloc(u8, 1024);
    defer allocator.free(buffer);

    std.debug.print("Allocated {} bytes\n", .{buffer.len});
}

std.mem.Allocator インターフェース

// Allocator は構造体(Rustのトレイトに相当)
pub const Allocator = struct {
    ptr: *anyopaque,
    vtable: *const VTable,

    pub const VTable = struct {
        alloc: *const fn(ctx: *anyopaque, len: usize, ptr_align: u8, ret_addr: usize) ?[*]u8,
        resize: *const fn(ctx: *anyopaque, buf: []u8, buf_align: u8, new_len: usize, ret_addr: usize) bool,
        free: *const fn(ctx: *anyopaque, buf: []u8, buf_align: u8, ret_addr: usize) void,
    };

    // 使いやすいラッパー関数
    pub fn alloc(self: Allocator, comptime T: type, n: usize) ![]T { ... }
    pub fn free(self: Allocator, memory: anytype) void { ... }
};

標準アロケータの種類

const std = @import("std");

pub fn main() !void {
    // 1. page_allocator(システムアロケータ)
    // - 直接OSからメモリを取得
    // - 遅いが、シンプル
    const page_alloc = std.heap.page_allocator;

    // 2. GeneralPurposeAllocator(汎用アロケータ)
    // - メモリリークを検出
    // - デバッグに最適
    var gpa = std.heap.GeneralPurposeAllocator(.{}){};
    defer {
        const leaked = gpa.deinit();
        if (leaked == .leak) {
            std.debug.print("Memory leak detected!\n", .{});
        }
    }
    const gpa_alloc = gpa.allocator();

    // 3. ArenaAllocator(アリーナアロケータ)
    // - まとめて解放
    // - 一時的なメモリに最適
    var arena = std.heap.ArenaAllocator.init(std.heap.page_allocator);
    defer arena.deinit();  // 全てまとめて解放
    const arena_alloc = arena.allocator();

    // 4. FixedBufferAllocator(固定バッファアロケータ)
    // - スタック上のバッファを使用
    // - OSコールなし
    var buffer: [1024]u8 = undefined;
    var fba = std.heap.FixedBufferAllocator.init(&buffer);
    const fba_alloc = fba.allocator();

    // 使用例
    const data1 = try page_alloc.alloc(u8, 100);
    defer page_alloc.free(data1);

    const data2 = try arena_alloc.alloc(u8, 200);
    // arena.deinit() で自動解放されるので個別のfreeは不要

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

アロケータの渡し方

const std = @import("std");

// 関数にアロケータを渡す
fn processData(allocator: std.mem.Allocator, size: usize) ![]u8 {
    const buffer = try allocator.alloc(u8, size);
    // 処理...
    return buffer;
}

// 構造体にアロケータを保持
const DataProcessor = struct {
    allocator: std.mem.Allocator,
    buffer: []u8,

    pub fn init(allocator: std.mem.Allocator, size: usize) !DataProcessor {
        const buffer = try allocator.alloc(u8, size);
        return DataProcessor{
            .allocator = allocator,
            .buffer = buffer,
        };
    }

    pub fn deinit(self: *DataProcessor) void {
        self.allocator.free(self.buffer);
    }

    pub fn process(self: *DataProcessor) void {
        std.debug.print("Processing {} bytes\n", .{self.buffer.len});
    }
};

pub fn main() !void {
    var gpa = std.heap.GeneralPurposeAllocator(.{}){};
    defer _ = gpa.deinit();
    const allocator = gpa.allocator();

    // パターン1: 関数に渡す
    const data = try processData(allocator, 1024);
    defer allocator.free(data);

    // パターン2: 構造体に保持
    var processor = try DataProcessor.init(allocator, 2048);
    defer processor.deinit();
    processor.process();
}

Rustとの比較:

// Rust: 所有権システム(暗黙的)
let vec = Vec::new();  // グローバルアロケータ
let data = vec![1, 2, 3];  // 自動管理
// スコープ終了でDrop
// Zig: 明示的アロケータ
var list = std.ArrayList(i32).init(allocator);  // 明示的
defer list.deinit();  // 明示的解放

学習ポイント:

  • アロケータは明示的に渡す
  • std.mem.Allocator はインターフェース
  • 用途に応じてアロケータを使い分け
  • ArenaAllocator は一時的なメモリに便利
  • Rustの所有権とは異なるアプローチ

GitHub


3. ポインタとスライス

シングルアイテムポインタ

const std = @import("std");

pub fn main() void {
    var x: i32 = 42;

    // ポインタの作成
    const ptr: *i32 = &x;
    const const_ptr: *const i32 = &x;

    // 参照外し
    std.debug.print("Value: {}\n", .{ptr.*});

    // ポインタ経由で変更
    ptr.* = 100;
    std.debug.print("New value: {}\n", .{x});

    // const ポインタは変更不可
    // const_ptr.* = 200;  // ❌ エラー
}

マルチアイテムポインタ

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

    // マルチアイテムポインタ(長さ情報なし)
    const ptr: [*]const i32 = &array;

    // インデックスでアクセス
    std.debug.print("ptr[0] = {}\n", .{ptr[0]});
    std.debug.print("ptr[1] = {}\n", .{ptr[1]});

    // 長さは自分で管理する必要がある
    for (0..array.len) |i| {
        std.debug.print("ptr[{}] = {}\n", .{i, ptr[i]});
    }
}

スライスの内部構造

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

    // スライス = ポインタ + 長さ
    const slice: []const i32 = &array;

    // スライスの構造
    std.debug.print("ptr: {*}\n", .{slice.ptr});
    std.debug.print("len: {}\n", .{slice.len});

    // スライスからポインタを取得
    const ptr: [*]const i32 = slice.ptr;
    std.debug.print("ptr[0] = {}\n", .{ptr[0]});
}

ポインタとスライスの変換

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

    // 配列 → スライス
    const slice: []i32 = &array;

    // スライス → ポインタ
    const ptr: [*]i32 = slice.ptr;

    // ポインタ → スライス(長さを指定)
    const new_slice: []i32 = ptr[0..5];

    // シングルアイテムポインタ → スライス(長さ1)
    var single: i32 = 42;
    const single_ptr: *i32 = &single;
    const single_slice: []i32 = single_ptr[0..1];

    std.debug.print("single_slice[0] = {}\n", .{single_slice[0]});
}

ポインタの実践例

const std = @import("std");

// 関数外の値を変更
fn increment(ptr: *i32) void {
    ptr.* += 1;
}

// 配列を操作
fn fillArray(array: []i32, value: i32) void {
    for (array) |*item| {
        item.* = value;
    }
}

// スライスを返す
fn getSubslice(data: []const u8, start: usize, end: usize) []const u8 {
    return data[start..end];
}

pub fn main() void {
    var x: i32 = 10;
    increment(&x);
    std.debug.print("x = {}\n", .{x});  // 11

    var array = [_]i32{0} ** 5;
    fillArray(&array, 42);
    std.debug.print("array = {any}\n", .{array});  // [42, 42, 42, 42, 42]

    const data = "Hello, Zig!";
    const sub = getSubslice(data, 0, 5);
    std.debug.print("sub = {s}\n", .{sub});  // "Hello"
}

Rustとの比較:

// Rust: 借用
fn increment(x: &mut i32) {
    *x += 1;
}

let mut x = 10;
increment(&mut x);
// Zig: ポインタ
fn increment(ptr: *i32) void {
    ptr.* += 1;
}

var x: i32 = 10;
increment(&x);

学習ポイント:

  • T: シングルアイテムポインタ
  • [*]T: マルチアイテムポインタ(長さなし)
  • []T: スライス(ポインタ + 長さ)
  • ポインタは明示的な参照外し(.*
  • Rustの借用より低レベル

GitHub


4. ユニオンとタグ付きユニオン

基本的なユニオン

const std = @import("std");

// 通常のユニオン(タグなし)
const Value = union {
    int: i32,
    float: f64,
    boolean: bool,
};

pub fn main() void {
    var value = Value{ .int = 42 };

    // どのフィールドがアクティブかはプログラマが管理
    std.debug.print("int: {}\n", .{value.int});

    // 別のフィールドに変更
    value = Value{ .float = 3.14 };
    std.debug.print("float: {}\n", .{value.float});

    // 間違ったフィールドにアクセスすると未定義動作
    // std.debug.print("int: {}\n", .{value.int});  // ❌ UB
}

タグ付きユニオン(Rustの enum 相当)

const std = @import("std");

// タグ付きユニオン
const Result = union(enum) {
    ok: i32,
    err: []const u8,

    pub fn isOk(self: Result) bool {
        return switch (self) {
            .ok => true,
            .err => false,
        };
    }
};

pub fn main() void {
    const success = Result{ .ok = 42 };
    const failure = Result{ .err = "Something went wrong" };

    // switch でパターンマッチング
    switch (success) {
        .ok => |value| std.debug.print("Success: {}\n", .{value}),
        .err => |msg| std.debug.print("Error: {s}\n", .{msg}),
    }

    switch (failure) {
        .ok => |value| std.debug.print("Success: {}\n", .{value}),
        .err => |msg| std.debug.print("Error: {s}\n", .{msg}),
    }

    std.debug.print("success.isOk() = {}\n", .{success.isOk()});
    std.debug.print("failure.isOk() = {}\n", .{failure.isOk()});
}

複雑なタグ付きユニオン

const std = @import("std");

const Message = union(enum) {
    quit,
    move: struct { x: i32, y: i32 },
    write: []const u8,
    change_color: struct { r: u8, g: u8, b: u8 },

    pub fn handle(self: Message) void {
        switch (self) {
            .quit => std.debug.print("Quitting\n", .{}),
            .move => |pos| std.debug.print("Moving to ({}, {})\n", .{pos.x, pos.y}),
            .write => |text| std.debug.print("Writing: {s}\n", .{text}),
            .change_color => |color| std.debug.print("Color: RGB({}, {}, {})\n", .{color.r, color.g, color.b}),
        }
    }
};

pub fn main() void {
    const messages = [_]Message{
        Message.quit,
        Message{ .move = .{ .x = 10, .y = 20 } },
        Message{ .write = "Hello" },
        Message{ .change_color = .{ .r = 255, .g = 0, .b = 0 } },
    };

    for (messages) |msg| {
        msg.handle();
    }
}

Rustの enum との比較

// Rust
enum Message {
    Quit,
    Move { x: i32, y: i32 },
    Write(String),
    ChangeColor(u8, u8, u8),
}

impl Message {
    fn handle(&self) {
        match self {
            Message::Quit => println!("Quitting"),
            Message::Move { x, y } => println!("Moving to ({}, {})", x, y),
            Message::Write(text) => println!("Writing: {}", text),
            Message::ChangeColor(r, g, b) => println!("Color: RGB({}, {}, {})", r, g, b),
        }
    }
}

// Zig
const Message = union(enum) {
    quit,
    move: struct { x: i32, y: i32 },
    write: []const u8,
    change_color: struct { r: u8, g: u8, b: u8 },

    pub fn handle(self: Message) void {
        switch (self) {
            .quit => std.debug.print("Quitting\n", .{}),
            .move => |pos| std.debug.print("Moving to ({}, {})\n", .{pos.x, pos.y}),
            .write => |text| std.debug.print("Writing: {s}\n", .{text}),
            .change_color => |color| std.debug.print("Color: RGB({}, {}, {})\n", .{color.r, color.g, color.b}),
        }
    }
};

学習ポイント:

  • union は複数の型のいずれか
  • union(enum) でタグ付きユニオン
  • switch でパターンマッチング
  • Rustの enum とほぼ同等
  • |variable| で値を取り出す

GitHub


5. enum (列挙型)

enum の基本

const std = @import("std");

const Direction = enum {
    north,
    south,
    east,
    west,

    pub fn opposite(self: Direction) Direction {
        return switch (self) {
            .north => .south,
            .south => .north,
            .east => .west,
            .west => .east,
        };
    }
};

pub fn main() void {
    const dir = Direction.north;
    std.debug.print("Direction: {}\n", .{dir});
    std.debug.print("Opposite: {}\n", .{dir.opposite()});
}

整数値の割り当て

const std = @import("std");

const StatusCode = enum(u16) {
    ok = 200,
    created = 201,
    bad_request = 400,
    not_found = 404,
    internal_error = 500,
};

pub fn main() void {
    const code = StatusCode.ok;

    // enum → 整数
    const value: u16 = @intFromEnum(code);
    std.debug.print("Code: {}\n", .{value});  // 200

    // 整数 → enum
    const from_int = @as(StatusCode, @enumFromInt(404));
    std.debug.print("Status: {}\n", .{from_int});  // not_found
}

switch での使用

const std = @import("std");

const Color = enum {
    red,
    green,
    blue,
    yellow,

    pub fn toRGB(self: Color) struct { r: u8, g: u8, b: u8 } {
        return switch (self) {
            .red => .{ .r = 255, .g = 0, .b = 0 },
            .green => .{ .r = 0, .g = 255, .b = 0 },
            .blue => .{ .r = 0, .g = 0, .b = 255 },
            .yellow => .{ .r = 255, .g = 255, .b = 0 },
        };
    }
};

pub fn main() void {
    const color = Color.red;
    const rgb = color.toRGB();
    std.debug.print("RGB: ({}, {}, {})\n", .{rgb.r, rgb.g, rgb.b});
}

学習ポイント:

  • enum は名前付き定数
  • 整数値を割り当て可能
  • @intFromEnum@enumFromInt で変換
  • switch で網羅性チェック

GitHub


6. comptime の深堀り

comptime 変数と関数

const std = @import("std");

// コンパイル時に計算
comptime {
    var sum: i32 = 0;
    var i: i32 = 0;
    while (i < 10) : (i += 1) {
        sum += i;
    }
    std.debug.assert(sum == 45);
}

// コンパイル時関数
fn fibonacci(n: comptime_int) comptime_int {
    if (n <= 1) return n;
    return fibonacci(n - 1) + fibonacci(n - 2);
}

pub fn main() void {
    // コンパイル時に計算(実行時コストゼロ)
    const fib10 = comptime fibonacci(10);
    std.debug.print("fibonacci(10) = {}\n", .{fib10});
}

inline for / while

pub fn main() void {
    // inline for: ループをアンロール
    inline for (0..5) |i| {
        std.debug.print("{} ", .{i});
    }
    std.debug.print("\n", .{});

    // 配列の各要素に対して実行
    const types = [_]type{ i8, i16, i32, i64 };
    inline for (types) |T| {
        std.debug.print("Size of {}: {}\n", .{T, @sizeOf(T)});
    }
}

型生成

const std = @import("std");

// 型を返す関数
fn Pair(comptime T: type) type {
    return struct {
        first: T,
        second: T,

        pub fn init(first: T, second: T) @This() {
            return .{ .first = first, .second = second };
        }

        pub fn swap(self: *@This()) void {
            const temp = self.first;
            self.first = self.second;
            self.second = temp;
        }
    };
}

pub fn main() void {
    var pair_int = Pair(i32).init(10, 20);
    std.debug.print("Before: ({}, {})\n", .{pair_int.first, pair_int.second});

    pair_int.swap();
    std.debug.print("After: ({}, {})\n", .{pair_int.first, pair_int.second});

    var pair_str = Pair([]const u8).init("hello", "world");
    std.debug.print("Strings: ({s}, {s})\n", .{pair_str.first, pair_str.second});
}

コンパイル時実行の活用

const std = @import("std");

// 文字列の長さをコンパイル時に計算
fn compileTimeStrLen(comptime str: []const u8) comptime_int {
    return str.len;
}

// 配列を生成
fn generateArray(comptime size: usize, comptime value: i32) [size]i32 {
    var array: [size]i32 = undefined;
    for (&array) |*item| {
        item.* = value;
    }
    return array;
}

pub fn main() void {
    // コンパイル時に長さを計算
    const len = comptime compileTimeStrLen("Hello, Zig!");
    std.debug.print("Length: {}\n", .{len});

    // コンパイル時に配列を生成
    const array = comptime generateArray(5, 42);
    std.debug.print("Array: {any}\n", .{array});
}

Rustのマクロとの比較:

// Rust: マクロ
macro_rules! create_array {
    ($size:expr, $val:expr) => {
        [$val; $size]
    };
}

let array = create_array!(5, 42);
// Zig: comptime関数
fn generateArray(comptime size: usize, comptime value: i32) [size]i32 {
    var array: [size]i32 = undefined;
    for (&array) |*item| {
        item.* = value;
    }
    return array;
}

const array = comptime generateArray(5, 42);

学習ポイント:

  • comptime でコンパイル時実行
  • inline でループアンロール
  • 型を生成する関数
  • Rustのマクロより強力

GitHub


7. ジェネリクス

anytype による型推論

const std = @import("std");

// anytype: 呼び出し時に型推論
fn max(a: anytype, b: anytype) @TypeOf(a, b) {
    return if (a > b) a else b;
}

fn print(value: anytype) void {
    std.debug.print("Value: {any}\n", .{value});
}

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

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

    print(42);
    print("hello");
    print(true);
}

comptime type パラメータ

const std = @import("std");

// 明示的な型パラメータ
fn swap(comptime T: type, a: *T, b: *T) void {
    const temp = a.*;
    a.* = b.*;
    b.* = temp;
}

fn sum(comptime T: type, values: []const T) T {
    var result: T = 0;
    for (values) |value| {
        result += value;
    }
    return result;
}

pub fn main() void {
    var x: i32 = 10;
    var y: i32 = 20;
    swap(i32, &x, &y);
    std.debug.print("x = {}, y = {}\n", .{x, y});

    const numbers = [_]i32{ 1, 2, 3, 4, 5 };
    const total = sum(i32, &numbers);
    std.debug.print("Sum: {}\n", .{total});
}

型制約の実現

const std = @import("std");

// 型が特定のメソッドを持つことを要求
fn printable(comptime T: type) bool {
    return @hasDecl(T, "print");
}

fn callPrint(value: anytype) void {
    comptime {
        if (!printable(@TypeOf(value))) {
            @compileError("Type must have a 'print' method");
        }
    }
    value.print();
}

const PrintableInt = struct {
    value: i32,

    pub fn print(self: PrintableInt) void {
        std.debug.print("Value: {}\n", .{self.value});
    }
};

pub fn main() void {
    const p = PrintableInt{ .value = 42 };
    callPrint(p);

    // コンパイルエラー: 'print' メソッドがない
    // callPrint(42);
}

ジェネリック構造体

const std = @import("std");

fn Stack(comptime T: type) type {
    return struct {
        items: std.ArrayList(T),

        pub fn init(allocator: std.mem.Allocator) @This() {
            return .{
                .items = std.ArrayList(T).init(allocator),
            };
        }

        pub fn deinit(self: *@This()) void {
            self.items.deinit();
        }

        pub fn push(self: *@This(), item: T) !void {
            try self.items.append(item);
        }

        pub fn pop(self: *@This()) ?T {
            if (self.items.items.len == 0) return null;
            return self.items.pop();
        }
    };
}

pub fn main() !void {
    var gpa = std.heap.GeneralPurposeAllocator(.{}){};
    defer _ = gpa.deinit();
    const allocator = gpa.allocator();

    var stack = Stack(i32).init(allocator);
    defer stack.deinit();

    try stack.push(10);
    try stack.push(20);
    try stack.push(30);

    while (stack.pop()) |value| {
        std.debug.print("Popped: {}\n", .{value});
    }
}

Rustのトレイト境界との比較:

// Rust: トレイト境界
fn max<T: PartialOrd>(a: T, b: T) -> T {
    if a > b { a } else { b }
}
// Zig: anytype + comptime
fn max(a: anytype, b: anytype) @TypeOf(a, b) {
    return if (a > b) a else b;
}

学習ポイント:

  • anytype で型推論
  • comptime T: type で明示的な型パラメータ
  • @hasDecl で型制約を実現
  • Rustのトレイトより柔軟だが、型安全性は低い

GitHub


8. 動的データ構造(ArrayList

ArrayList

const std = @import("std");

pub fn main() !void {
    var gpa = std.heap.GeneralPurposeAllocator(.{}){};
    defer _ = gpa.deinit();
    const allocator = gpa.allocator();

    // ArrayList の作成
    var list = std.ArrayList(i32).init(allocator);
    defer list.deinit();

    // 要素の追加
    try list.append(10);
    try list.append(20);
    try list.append(30);

    std.debug.print("Length: {}\n", .{list.items.len});

    // 要素へのアクセス
    for (list.items, 0..) |item, i| {
        std.debug.print("[{}] = {}\n", .{i, item});
    }

    // 要素の削除
    _ = list.pop();

    // 挿入
    try list.insert(1, 15);

    // クリア
    list.clearRetainingCapacity();
}

HashMap

const std = @import("std");

pub fn main() !void {
    var gpa = std.heap.GeneralPurposeAllocator(.{}){};
    defer _ = gpa.deinit();
    const allocator = gpa.allocator();

    // HashMap の作成
    var map = std.AutoHashMap([]const u8, i32).init(allocator);
    defer map.deinit();

    // 要素の追加
    try map.put("alice", 30);
    try map.put("bob", 25);
    try map.put("charlie", 35);

    // 要素の取得
    if (map.get("alice")) |age| {
        std.debug.print("Alice is {} years old\n", .{age});
    }

    // 要素の存在確認
    if (map.contains("bob")) {
        std.debug.print("Bob exists\n", .{});
    }

    // イテレート
    var iter = map.iterator();
    while (iter.next()) |entry| {
        std.debug.print("{s}: {}\n", .{entry.key_ptr.*, entry.value_ptr.*});
    }

    // 削除
    _ = map.remove("charlie");
}

アロケータの実践

const std = @import("std");

fn processData(allocator: std.mem.Allocator) !void {
    // 一時的なデータ構造
    var list = std.ArrayList([]const u8).init(allocator);
    defer list.deinit();

    try list.append("hello");
    try list.append("world");
    try list.append("zig");

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

pub fn main() !void {
    // ArenaAllocator で一括管理
    var arena = std.heap.ArenaAllocator.init(std.heap.page_allocator);
    defer arena.deinit();  // 全てまとめて解放

    const allocator = arena.allocator();

    try processData(allocator);

    // deinit 忘れても arena.deinit() で解放される
}

カスタムデータ構造

const std = @import("std");

const Queue = struct {
    items: std.ArrayList(i32),

    pub fn init(allocator: std.mem.Allocator) Queue {
        return Queue{
            .items = std.ArrayList(i32).init(allocator),
        };
    }

    pub fn deinit(self: *Queue) void {
        self.items.deinit();
    }

    pub fn enqueue(self: *Queue, value: i32) !void {
        try self.items.append(value);
    }

    pub fn dequeue(self: *Queue) ?i32 {
        if (self.items.items.len == 0) return null;
        return self.items.orderedRemove(0);
    }

    pub fn isEmpty(self: Queue) bool {
        return self.items.items.len == 0;
    }
};

pub fn main() !void {
    var gpa = std.heap.GeneralPurposeAllocator(.{}){};
    defer _ = gpa.deinit();
    const allocator = gpa.allocator();

    var queue = Queue.init(allocator);
    defer queue.deinit();

    try queue.enqueue(1);
    try queue.enqueue(2);
    try queue.enqueue(3);

    while (queue.dequeue()) |value| {
        std.debug.print("Dequeued: {}\n", .{value});
    }
}

Rustとの比較:

// Rust: Vec と HashMap
let mut vec = Vec::new();
vec.push(10);

let mut map = HashMap::new();
map.insert("key", "value");
// Zig: ArrayList と HashMap
var list = std.ArrayList(i32).init(allocator);
try list.append(10);

var map = std.AutoHashMap([]const u8, []const u8).init(allocator);
try map.put("key", "value");

学習ポイント:

  • ArrayList は動的配列
  • AutoHashMap はハッシュマップ
  • 全ての動的データ構造にアロケータが必要
  • defer で確実に解放

GitHub


9. パッケージとモジュール

@import の詳細

// src/math.zig
pub fn add(a: i32, b: i32) i32 {
    return a + b;
}

pub fn multiply(a: i32, b: i32) i32 {
    return a * b;
}

const PI = 3.14159;  // プライベート

pub const E = 2.71828;  // 公開
// src/main.zig
const std = @import("std");
const math = @import("math.zig");

pub fn main() void {
    const sum = math.add(10, 20);
    const product = math.multiply(5, 6);

    std.debug.print("Sum: {}\n", .{sum});
    std.debug.print("Product: {}\n", .{product});
    std.debug.print("E: {}\n", .{math.E});

    // math.PI は使えない(プライベート)
}

pub による公開制御

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

pub const User = struct {
    id: u32,
    name: []const u8,
    email: []const u8,
    password_hash: []const u8,  // プライベートフィールド

    // 公開コンストラクタ
    pub fn init(id: u32, name: []const u8, email: []const u8) User {
        return User{
            .id = id,
            .name = name,
            .email = email,
            .password_hash = "",  // 仮の値
        };
    }

    // 公開メソッド
    pub fn getName(self: User) []const u8 {
        return self.name;
    }

    // プライベートメソッド
    fn hashPassword(password: []const u8) []const u8 {
        // パスワードをハッシュ化
        return password;  // 簡略化
    }
};

// 公開関数
pub fn validateEmail(email: []const u8) bool {
    return std.mem.indexOf(u8, email, "@") != null;
}

// プライベート関数
fn internalHelper() void {
    // ...
}
// src/main.zig
const std = @import("std");
const user_mod = @import("user.zig");

pub fn main() void {
    const user = user_mod.User.init(1, "Alice", "alice@example.com");

    std.debug.print("Name: {s}\n", .{user.getName()});

    // user.password_hash は直接アクセスできない
    // user_mod.internalHelper() は呼べない

    if (user_mod.validateEmail("test@example.com")) {
        std.debug.print("Valid email\n", .{});
    }
}

パッケージ構成

my-project/
├── build.zig
├── build.zig.zon
└── src/
    ├── main.zig
    ├── models/
    │   ├── user.zig
    │   └── post.zig
    ├── services/
    │   ├── auth.zig
    │   └── database.zig
    └── utils/
        └── helpers.zig
// src/models/user.zig
pub const User = struct {
    id: u32,
    name: []const u8,
};
// src/main.zig
const std = @import("std");
const User = @import("models/user.zig").User;

pub fn main() void {
    const user = User{ .id = 1, .name = "Alice" };
    std.debug.print("User: {s}\n", .{user.name});
}

build.zig.zon(依存関係)

// build.zig.zon
.{
    .name = "my-project",
    .version = "0.1.0",
    .dependencies = .{
        .@"some-package" = .{
            .url = "https://github.com/user/some-package/archive/main.tar.gz",
            .hash = "1220...",
        },
    },
}
// build.zig
const std = @import("std");

pub fn build(b: *std.Build) void {
    const target = b.standardTargetOptions(.{});
    const optimize = b.standardOptimizeOption(.{});

    const exe = b.addExecutable(.{
        .name = "my-project",
        .root_source_file = .{ .path = "src/main.zig" },
        .target = target,
        .optimize = optimize,
    });

    // 依存パッケージを追加
    const some_package = b.dependency("some-package", .{
        .target = target,
        .optimize = optimize,
    });
    exe.root_module.addImport("some-package", some_package.module("some-package"));

    b.installArtifact(exe);
}

学習ポイント:

  • @import でモジュールをインポート
  • pub で公開制御
  • ディレクトリ構成は自由
  • build.zig.zon で依存管理

GitHub


10. よく使う標準ライブラリ

std.mem (メモリ操作)

const std = @import("std");

pub fn main() !void {
    // メモリのコピー
    var src = [_]u8{ 1, 2, 3, 4, 5 };
    var dst: [5]u8 = undefined;
    @memcpy(&dst, &src);

    // メモリの比較
    const equal = std.mem.eql(u8, &src, &dst);
    std.debug.print("Equal: {}\n", .{equal});

    // 文字列の操作
    const text = "Hello, Zig!";

    // 文字列の検索
    if (std.mem.indexOf(u8, text, "Zig")) |index| {
        std.debug.print("Found at index {}\n", .{index});
    }

    // 文字列の分割
    var iter = std.mem.splitSequence(u8, text, ", ");
    while (iter.next()) |part| {
        std.debug.print("Part: {s}\n", .{part});
    }

    // トリム
    const whitespace = "  hello  ";
    const trimmed = std.mem.trim(u8, whitespace, " ");
    std.debug.print("Trimmed: '{s}'\n", .{trimmed});
}

std.fs (ファイル操作)

const std = @import("std");

pub fn main() !void {
    var gpa = std.heap.GeneralPurposeAllocator(.{}){};
    defer _ = gpa.deinit();
    const allocator = gpa.allocator();

    // ファイルの書き込み
    const file = try std.fs.cwd().createFile("test.txt", .{});
    defer file.close();

    try file.writeAll("Hello, Zig!\n");
    try file.writeAll("This is a test.\n");

    // ファイルの読み込み
    const content = try std.fs.cwd().readFileAlloc(
        allocator,
        "test.txt",
        1024 * 1024,  // 最大サイズ
    );
    defer allocator.free(content);

    std.debug.print("Content:\n{s}\n", .{content});

    // ディレクトリの作成
    try std.fs.cwd().makeDir("test_dir");

    // ディレクトリの削除
    try std.fs.cwd().deleteDir("test_dir");

    // ファイルの削除
    try std.fs.cwd().deleteFile("test.txt");
}

std.json (JSON処理)

const std = @import("std");

const User = struct {
    id: u32,
    name: []const u8,
    email: []const u8,
    active: bool,
};

pub fn main() !void {
    var gpa = std.heap.GeneralPurposeAllocator(.{}){};
    defer _ = gpa.deinit();
    const allocator = gpa.allocator();

    // JSONのシリアライズ
    const user = User{
        .id = 1,
        .name = "Alice",
        .email = "alice@example.com",
        .active = true,
    };

    var buffer = std.ArrayList(u8).init(allocator);
    defer buffer.deinit();

    try std.json.stringify(user, .{}, buffer.writer());

    std.debug.print("JSON: {s}\n", .{buffer.items});

    // JSONのデシリアライズ
    const json_text =
        \\{
        \\  "id": 2,
        \\  "name": "Bob",
        \\  "email": "bob@example.com",
        \\  "active": false
        \\}
    ;

    const parsed = try std.json.parseFromSlice(
        User,
        allocator,
        json_text,
        .{},
    );
    defer parsed.deinit();

    std.debug.print("Parsed: {s}\n", .{parsed.value.name});
}

実践的な例:CLIツール

const std = @import("std");

pub fn main() !void {
    var gpa = std.heap.GeneralPurposeAllocator(.{}){};
    defer _ = gpa.deinit();
    const allocator = gpa.allocator();

    // コマンドライン引数を取得
    const args = try std.process.argsAlloc(allocator);
    defer std.process.argsFree(allocator, args);

    if (args.len < 2) {
        std.debug.print("Usage: {s} <filename>\n", .{args[0]});
        return;
    }

    const filename = args[1];

    // ファイルを読み込み
    const content = std.fs.cwd().readFileAlloc(
        allocator,
        filename,
        1024 * 1024,
    ) catch |err| {
        std.debug.print("Error reading file: {}\n", .{err});
        return;
    };
    defer allocator.free(content);

    // 行数と文字数をカウント
    var line_count: usize = 0;
    var char_count: usize = content.len;

    for (content) |c| {
        if (c == '\n') line_count += 1;
    }

    std.debug.print("Lines: {}\n", .{line_count});
    std.debug.print("Characters: {}\n", .{char_count});
}

学習ポイント:

  • std.mem でメモリ操作
  • std.fs でファイル操作
  • std.json でJSON処理
  • 標準ライブラリは豊富

GitHub


11. まとめ

Intermediate編で学んだこと

エラー処理の深掘り

  • エラーセットの組み合わせ(||
  • エラー推論(anyerror
  • カスタムエラーの設計
  • errdefer の実践

メモリアロケータ

  • アロケータの概念
  • 標準アロケータ(page, GPA, Arena, FixedBuffer)
  • 明示的なメモリ管理
  • Rustとの違い

ポインタとスライス

  • シングルアイテムポインタ(T
  • マルチアイテムポインタ([*]T
  • スライスの内部構造
  • ポインタの実践

タグ付きユニオン

  • union(enum) の使い方
  • パターンマッチング
  • Rustの enum との比較

comptime

  • コンパイル時計算
  • 型生成
  • ジェネリクス
  • メタプログラミング

動的データ構造

  • ArrayList
  • HashMap
  • アロケータの実践

パッケージとモジュール

  • @import
  • pub による公開制御
  • build.zig.zon

Rust vs Zig の理解

概念 Rust Zig
メモリ管理 所有権(暗黙的) アロケータ(明示的)
エラー Result error union (!T)
ジェネリクス トレイト境界 comptime + anytype
enum enum + match union(enum) + switch
ポインタ &T, &mut T *T, *const T
マクロ macro_rules!, proc_macro comptime

実践的なCLIツールの例

const std = @import("std");

pub fn main() !void {
    var gpa = std.heap.GeneralPurposeAllocator(.{}){};
    defer _ = gpa.deinit();
    const allocator = gpa.allocator();

    // 引数の処理
    const args = try std.process.argsAlloc(allocator);
    defer std.process.argsFree(allocator, args);

    // ファイル操作
    var list = std.ArrayList([]const u8).init(allocator);
    defer list.deinit();

    // JSON処理
    const json_text = try std.json.stringifyAlloc(
        allocator,
        list.items,
        .{},
    );
    defer allocator.free(json_text);

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

公式リソース:

0
1
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
0
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?