第5章: 制御構造

学習目標

この章を終えると、以下ができるようになります:

  • if文とif式を使い分けられる
  • whileループとforループを適切に使用できる
  • switchによるパターンマッチングを活用できる
  • ブロックラベルと制御フローを理解できる
  • if文とif式

    基本的なif文

    const std = @import("std");
    
    pub fn example() void {
        const x = 10;
    
        if (x > 5) {
            std.debug.print("x is greater than 5\n", .{});
        }
    
        if (x > 5) {
            std.debug.print("x is greater than 5\n", .{});
        } else {
            std.debug.print("x is 5 or less\n", .{});
        }
    }
    

    if式

    Zigでは、ifは式として使用でき、値を返すことができます。

    const std = @import("std");
    
    pub fn example() void {
        const x = 10;
    
        // if式で値を返す
        const result = if (x > 5) "greater" else "smaller";
        std.debug.print("Result: {s}\n", .{result});
    
        // 複雑な式
        const value = if (x > 10)
            100
        else if (x > 5)
            50
        else
            0;
    
        std.debug.print("Value: {}\n", .{value});
    }
    

    オプショナル型のアンラップ

    const std = @import("std");
    
    pub fn example() void {
        const maybe_number: ?i32 = 42;
    
        // オプショナル型のアンラップ
        if (maybe_number) |value| {
            std.debug.print("Value exists: {}\n", .{value});
        } else {
            std.debug.print("Value is null\n", .{});
        }
    
        // ポインタのアンラップ
        var x: i32 = 100;
        var maybe_ptr: ?*i32 = &x;
    
        if (maybe_ptr) |ptr| {
            std.debug.print("Pointer value: {}\n", .{ptr.*});
        }
    }
    

    エラーユニオンのアンラップ

    const std = @import("std");
    
    fn riskyOperation() !i32 {
        return 42;
    }
    
    pub fn example() void {
        // エラーユニオンのアンラップ
        if (riskyOperation()) |value| {
            std.debug.print("Success: {}\n", .{value});
        } else |err| {
            std.debug.print("Error: {}\n", .{err});
        }
    }
    

    whileループ

    基本的なwhileループ

    const std = @import("std");
    
    pub fn example() void {
        var i: usize = 0;
    
        while (i < 5) {
            std.debug.print("i = {}\n", .{i});
            i += 1;
        }
    }
    

    continueとbreak

    const std = @import("std");
    
    pub fn example() void {
        var i: usize = 0;
    
        while (i < 10) : (i += 1) {
            if (i == 3) {
                continue; // 3をスキップ
            }
            if (i == 7) {
                break; // 7で終了
            }
            std.debug.print("{} ", .{i});
        }
        std.debug.print("\n", .{});
    }
    

    while式

    const std = @import("std");
    
    pub fn example() void {
        var i: usize = 0;
    
        // whileは式として使える
        const result = while (i < 10) : (i += 1) {
            if (i == 5) {
                break i; // 5を返す
            }
        } else 0; // breakしなかった場合
    
        std.debug.print("Result: {}\n", .{result});
    }
    

    オプショナルとwhile

    const std = @import("std");
    
    fn getNext() ?i32 {
        // シミュレーション
        return null;
    }
    
    pub fn example() void {
        // オプショナル型の値がnullになるまでループ
        var value: ?i32 = 10;
        while (value) |v| : (value = getNext()) {
            std.debug.print("Value: {}\n", .{v});
        }
    }
    

    forループ

    配列のイテレーション

    const std = @import("std");
    
    pub fn example() void {
        const numbers = [_]i32{ 1, 2, 3, 4, 5 };
    
        // 値のイテレーション
        for (numbers) |num| {
            std.debug.print("{} ", .{num});
        }
        std.debug.print("\n", .{});
    
        // インデックス付きイテレーション
        for (numbers, 0..) |num, i| {
            std.debug.print("[{}]={} ", .{i, num});
        }
        std.debug.print("\n", .{});
    }
    

    スライスのイテレーション

    const std = @import("std");
    
    pub fn example() void {
        const items = [_]i32{ 10, 20, 30, 40, 50 };
        const slice = items[1..4]; // [20, 30, 40]
    
        for (slice) |item| {
            std.debug.print("{} ", .{item});
        }
        std.debug.print("\n", .{});
    }
    

    範囲のイテレーション

    const std = @import("std");
    
    pub fn example() void {
        // 0から9まで
        for (0..10) |i| {
            std.debug.print("{} ", .{i});
        }
        std.debug.print("\n", .{});
    }
    

    複数の配列の同時イテレーション

    const std = @import("std");
    
    pub fn example() void {
        const a = [_]i32{ 1, 2, 3 };
        const b = [_]i32{ 4, 5, 6 };
    
        // 2つの配列を同時にイテレート
        for (a, b) |x, y| {
            std.debug.print("{} + {} = {}\n", .{x, y, x + y});
        }
    }
    

    for式

    const std = @import("std");
    
    pub fn example() void {
        const numbers = [_]i32{ 1, 2, 3, 4, 5 };
    
        // forは式として使える
        const sum = blk: {
            var total: i32 = 0;
            for (numbers) |num| {
                total += num;
            }
            break :blk total;
        };
    
        std.debug.print("Sum: {}\n", .{sum});
    }
    

    switch文

    基本的なswitch

    const std = @import("std");
    
    pub fn example() void {
        const x = 2;
    
        switch (x) {
            1 => std.debug.print("One\n", .{}),
            2 => std.debug.print("Two\n", .{}),
            3 => std.debug.print("Three\n", .{}),
            else => std.debug.print("Other\n", .{}),
        }
    }
    

    複数のケース

    const std = @import("std");
    
    pub fn example() void {
        const x = 5;
    
        switch (x) {
            1, 2, 3 => std.debug.print("Small\n", .{}),
            4, 5, 6 => std.debug.print("Medium\n", .{}),
            7, 8, 9 => std.debug.print("Large\n", .{}),
            else => std.debug.print("Other\n", .{}),
        }
    }
    

    範囲のマッチング

    const std = @import("std");
    
    pub fn example() void {
        const x = 42;
    
        switch (x) {
            0...10 => std.debug.print("0-10\n", .{}),
            11...50 => std.debug.print("11-50\n", .{}),
            51...100 => std.debug.print("51-100\n", .{}),
            else => std.debug.print("Other\n", .{}),
        }
    }
    

    switch式

    const std = @import("std");
    
    pub fn example() void {
        const day = 3;
    
        const day_name = switch (day) {
            1 => "Monday",
            2 => "Tuesday",
            3 => "Wednesday",
            4 => "Thursday",
            5 => "Friday",
            6 => "Saturday",
            7 => "Sunday",
            else => "Invalid",
        };
    
        std.debug.print("Day: {s}\n", .{day_name});
    }
    

    タグ付きユニオンとswitch

    const std = @import("std");
    
    const Shape = union(enum) {
        circle: f64,        // 半径
        rectangle: struct {
            width: f64,
            height: f64,
        },
        triangle: struct {
            base: f64,
            height: f64,
        },
    };
    
    fn area(shape: Shape) f64 {
        return switch (shape) {
            .circle => |radius| 3.14159 * radius * radius,
            .rectangle => |rect| rect.width * rect.height,
            .triangle => |tri| 0.5 * tri.base * tri.height,
        };
    }
    
    pub fn example() void {
        const circle = Shape{ .circle = 5.0 };
        const rect = Shape{ .rectangle = .{ .width = 4.0, .height = 6.0 } };
    
        std.debug.print("Circle area: {d:.2}\n", .{area(circle)});
        std.debug.print("Rectangle area: {d:.2}\n", .{area(rect)});
    }
    

    列挙型とswitch

    const std = @import("std");
    
    const Color = enum {
        red,
        green,
        blue,
        yellow,
    };
    
    pub fn example() void {
        const color = Color.green;
    
        const color_name = switch (color) {
            .red => "Red",
            .green => "Green",
            .blue => "Blue",
            .yellow => "Yellow",
        };
    
        std.debug.print("Color: {s}\n", .{color_name});
    }
    

    ブロックラベル

    名前付きブロック

    const std = @import("std");
    
    pub fn example() void {
        const result = blk: {
            const x = 10;
            const y = 20;
            break :blk x + y;
        };
    
        std.debug.print("Result: {}\n", .{result});
    }
    

    ネストしたループからの脱出

    const std = @import("std");
    
    pub fn example() void {
        outer: for (0..5) |i| {
            for (0..5) |j| {
                if (i * j > 6) {
                    std.debug.print("Breaking at i={}, j={}\n", .{i, j});
                    break :outer; // 外側のループから脱出
                }
                std.debug.print("({}, {}) ", .{i, j});
            }
            std.debug.print("\n", .{});
        }
    }
    

    ラベル付きcontinue

    const std = @import("std");
    
    pub fn example() void {
        outer: for (0..3) |i| {
            for (0..3) |j| {
                if (j == 1) {
                    continue :outer; // 外側のループの次のイテレーション
                }
                std.debug.print("({}, {}) ", .{i, j});
            }
            std.debug.print("\n", .{});
        }
    }
    

    defer文

    基本的なdefer

    const std = @import("std");
    
    pub fn example() !void {
        const allocator = std.heap.page_allocator;
    
        const buffer = try allocator.alloc(u8, 1024);
        defer allocator.free(buffer); // スコープ終了時に自動解放
    
        // bufferを使用...
        std.debug.print("Buffer size: {}\n", .{buffer.len});
    }
    

    複数のdefer

    const std = @import("std");
    
    pub fn example() void {
        defer std.debug.print("First defer\n", .{});
        defer std.debug.print("Second defer\n", .{});
        defer std.debug.print("Third defer\n", .{});
    
        std.debug.print("Main code\n", .{});
    }
    
    // 出力:
    // Main code
    // Third defer
    // Second defer
    // First defer
    

    errdefer

    const std = @import("std");
    
    fn allocateResources(allocator: std.mem.Allocator) !void {
        const buffer1 = try allocator.alloc(u8, 1024);
        errdefer allocator.free(buffer1); // エラー時のみ解放
    
        const buffer2 = try allocator.alloc(u8, 2048);
        errdefer allocator.free(buffer2);
    
        // エラーが発生したら、errdefer が実行される
        return error.SimulatedError;
    }
    
    pub fn example() !void {
        const allocator = std.heap.page_allocator;
        allocateResources(allocator) catch |err| {
            std.debug.print("Error occurred: {}\n", .{err});
        };
    }
    

    実践例

    例1: 配列の検索

    const std = @import("std");
    
    fn findMax(numbers: []const i32) ?i32 {
        if (numbers.len == 0) return null;
    
        var max = numbers[0];
        for (numbers[1..]) |num| {
            if (num > max) {
                max = num;
            }
        }
        return max;
    }
    
    pub fn main() void {
        const numbers = [_]i32{ 3, 7, 2, 9, 1, 5 };
    
        if (findMax(&numbers)) |max| {
            std.debug.print("Max: {}\n", .{max});
        } else {
            std.debug.print("Empty array\n", .{});
        }
    }
    

    例2: フィボナッチ数列

    const std = @import("std");
    
    fn fibonacci(n: u32) u64 {
        if (n <= 1) return n;
    
        var a: u64 = 0;
        var b: u64 = 1;
        var i: u32 = 2;
    
        while (i <= n) : (i += 1) {
            const tmp = a + b;
            a = b;
            b = tmp;
        }
    
        return b;
    }
    
    pub fn main() void {
        for (0..10) |i| {
            const fib = fibonacci(@intCast(i));
            std.debug.print("fib({}) = {}\n", .{i, fib});
        }
    }
    

    例3: 文字列のパース

    const std = @import("std");
    
    const TokenType = enum {
        number,
        operator,
        unknown,
    };
    
    fn getTokenType(c: u8) TokenType {
        return switch (c) {
            '0'...'9' => .number,
            '+', '-', '*', '/' => .operator,
            else => .unknown,
        };
    }
    
    pub fn main() void {
        const input = "123+456";
    
        for (input) |c| {
            const token_type = getTokenType(c);
            std.debug.print("'{c}' -> {}\n", .{c, token_type});
        }
    }
    

    例4: エラーハンドリング

    const std = @import("std");
    
    const ParseError = error{
        InvalidDigit,
        Overflow,
    };
    
    fn parseDigit(c: u8) ParseError!u8 {
        return switch (c) {
            '0'...'9' => c - '0',
            else => error.InvalidDigit,
        };
    }
    
    pub fn main() !void {
        const chars = "0123456789X";
    
        for (chars) |c| {
            if (parseDigit(c)) |digit| {
                std.debug.print("'{c}' = {}\n", .{c, digit});
            } else |err| {
                std.debug.print("'{c}' -> Error: {}\n", .{c, err});
            }
        }
    }
    

    まとめ

    この章では、Zigの制御構造について学びました:

  • if文/式: 条件分岐、オプショナルのアンラップ、エラーハンドリング
  • whileループ: 条件ループ、continue/break、while式
  • forループ: 配列/スライスのイテレーション、範囲ループ
  • switch: パターンマッチング、タグ付きユニオン、列挙型
  • ブロックラベル: 名前付きブロック、ネストしたループの制御
  • defer/errdefer: リソース管理、エラー時のクリーンアップ
  • 次の章では、関数について学びます。

    参考資料

  • Zig Language Reference - Control Flow: https://ziglang.org/documentation/master/#Control-Flow
  • Ziglearn - Flow Control: https://ziglearn.org/chapter-1/#flow-control