第6章: 関数

学習目標

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

  • 関数を定義し、適切に呼び出すことができる
  • パラメータと戻り値を理解する
  • comptime関数でメタプログラミングができる
  • 関数ポインタとクロージャを使用できる
  • 関数の基本

    関数の定義

    const std = @import("std");
    
    // 基本的な関数
    fn add(a: i32, b: i32) i32 {
        return a + b;
    }
    
    // void を返す関数
    fn greet(name: []const u8) void {
        std.debug.print("Hello, {s}!\n", .{name});
    }
    
    // エラーを返す可能性がある関数
    fn divide(a: f64, b: f64) !f64 {
        if (b == 0.0) {
            return error.DivisionByZero;
        }
        return a / b;
    }
    
    pub fn main() !void {
        const result = add(10, 20);
        std.debug.print("10 + 20 = {}\n", .{result});
    
        greet("Zig");
    
        if (divide(10.0, 2.0)) |value| {
            std.debug.print("10 / 2 = {d:.2}\n", .{value});
        } else |err| {
            std.debug.print("Error: {}\n", .{err});
        }
    }
    

    関数の可視性

    // プライベート関数(ファイル内でのみ使用可能)
    fn privateFunction() void {
        // ...
    }
    
    // パブリック関数(エクスポート可能)
    pub fn publicFunction() void {
        // ...
    }
    
    // エクスポート関数(C言語から呼び出し可能)
    export fn exportedFunction() void {
        // ...
    }
    

    関数のパラメータ

    const std = @import("std");
    
    // 値渡し
    fn increment(x: i32) i32 {
        return x + 1; // xは変更されない
    }
    
    // ポインタ渡し
    fn incrementPtr(x: *i32) void {
        x.* += 1; // xが指す値を変更
    }
    
    // スライス
    fn sumSlice(numbers: []const i32) i64 {
        var total: i64 = 0;
        for (numbers) |num| {
            total += num;
        }
        return total;
    }
    
    pub fn main() void {
        const x = 10;
        const result = increment(x);
        std.debug.print("x={}, result={}\n", .{x, result});
    
        var y: i32 = 10;
        incrementPtr(&y);
        std.debug.print("y={}\n", .{y});
    
        const numbers = [_]i32{ 1, 2, 3, 4, 5 };
        const sum = sumSlice(&numbers);
        std.debug.print("sum={}\n", .{sum});
    }
    

    デフォルト引数

    Zigには直接的なデフォルト引数はありませんが、オプショナル型で代用できます。

    const std = @import("std");
    
    fn greet(name: []const u8, greeting: ?[]const u8) void {
        const actual_greeting = greeting orelse "Hello";
        std.debug.print("{s}, {s}!\n", .{actual_greeting, name});
    }
    
    pub fn main() void {
        greet("Alice", null);           // Hello, Alice!
        greet("Bob", "Hi");             // Hi, Bob!
    }
    

    戻り値

    単一の戻り値

    fn square(x: i32) i32 {
        return x * x;
    }
    

    複数の値を返す(構造体を使用)

    const std = @import("std");
    
    const DivResult = struct {
        quotient: i32,
        remainder: i32,
    };
    
    fn divmod(a: i32, b: i32) DivResult {
        return DivResult{
            .quotient = @divTrunc(a, b),
            .remainder = @mod(a, b),
        };
    }
    
    pub fn main() void {
        const result = divmod(17, 5);
        std.debug.print("17 / 5 = {} remainder {}\n",
            .{result.quotient, result.remainder});
    }
    

    オプショナルな戻り値

    const std = @import("std");
    
    fn findFirst(numbers: []const i32, target: i32) ?usize {
        for (numbers, 0..) |num, index| {
            if (num == target) {
                return index;
            }
        }
        return null;
    }
    
    pub fn main() void {
        const numbers = [_]i32{ 1, 2, 3, 4, 5 };
    
        if (findFirst(&numbers, 3)) |index| {
            std.debug.print("Found at index {}\n", .{index});
        } else {
            std.debug.print("Not found\n", .{});
        }
    }
    

    エラーユニオン

    const std = @import("std");
    
    const MathError = error{
        DivisionByZero,
        NegativeSquareRoot,
    };
    
    fn safeSqrt(x: f64) MathError!f64 {
        if (x < 0.0) {
            return error.NegativeSquareRoot;
        }
        return std.math.sqrt(x);
    }
    
    pub fn main() !void {
        const result = try safeSqrt(16.0);
        std.debug.print("sqrt(16) = {d:.2}\n", .{result});
    
        // エラーをキャッチ
        safeSqrt(-4.0) catch |err| {
            std.debug.print("Error: {}\n", .{err});
        };
    }
    

    コンパイル時関数(comptime)

    基本的なcomptime関数

    const std = @import("std");
    
    // コンパイル時に実行される関数
    fn factorial(comptime n: u32) u32 {
        if (n <= 1) return 1;
        return n * factorial(n - 1);
    }
    
    pub fn main() void {
        // コンパイル時に計算される
        const fact_5 = factorial(5);  // 120
        const fact_10 = factorial(10); // 3628800
    
        std.debug.print("5! = {}\n", .{fact_5});
        std.debug.print("10! = {}\n", .{fact_10});
    }
    

    ジェネリック関数

    const std = @import("std");
    
    // 型をパラメータとして受け取る
    fn max(comptime T: type, a: T, b: T) T {
        return if (a > b) a else b;
    }
    
    // 配列の最大値を見つける
    fn findMax(comptime T: type, array: []const T) ?T {
        if (array.len == 0) return null;
    
        var result = array[0];
        for (array[1..]) |item| {
            if (item > result) {
                result = item;
            }
        }
        return result;
    }
    
    pub fn main() void {
        // さまざまな型で使用
        const max_int = max(i32, 10, 20);
        const max_float = max(f64, 3.14, 2.71);
    
        std.debug.print("max(10, 20) = {}\n", .{max_int});
        std.debug.print("max(3.14, 2.71) = {d:.2}\n", .{max_float});
    
        const numbers = [_]i32{ 3, 7, 2, 9, 1 };
        if (findMax(i32, &numbers)) |m| {
            std.debug.print("max of array = {}\n", .{m});
        }
    }
    

    comptime パラメータの活用

    const std = @import("std");
    
    // コンパイル時にサイズが決まる配列を生成
    fn createArray(comptime T: type, comptime size: usize, value: T) [size]T {
        var result: [size]T = undefined;
        for (&result) |*item| {
            item.* = value;
        }
        return result;
    }
    
    pub fn main() void {
        const arr = createArray(i32, 5, 42);
        std.debug.print("Array: ", .{});
        for (arr) |item| {
            std.debug.print("{} ", .{item});
        }
        std.debug.print("\n", .{});
    }
    

    型情報を使ったジェネリック関数

    const std = @import("std");
    
    fn printTypeInfo(comptime T: type) void {
        const type_info = @typeInfo(T);
    
        std.debug.print("Type: {}\n", .{T});
        std.debug.print("Size: {} bytes\n", .{@sizeOf(T)});
        std.debug.print("Alignment: {} bytes\n", .{@alignOf(T)});
    
        switch (type_info) {
            .Int => |int_info| {
                std.debug.print("Integer: {} bits, {}\n",
                    .{int_info.bits, int_info.signedness});
            },
            .Float => |float_info| {
                std.debug.print("Float: {} bits\n", .{float_info.bits});
            },
            else => {},
        }
    }
    
    pub fn main() void {
        printTypeInfo(i32);
        std.debug.print("\n", .{});
        printTypeInfo(f64);
    }
    

    再帰

    基本的な再帰

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

    末尾再帰

    const std = @import("std");
    
    fn factorialTailRec(n: u64, accumulator: u64) u64 {
        if (n <= 1) return accumulator;
        return factorialTailRec(n - 1, n * accumulator);
    }
    
    fn factorial(n: u64) u64 {
        return factorialTailRec(n, 1);
    }
    
    pub fn main() void {
        std.debug.print("10! = {}\n", .{factorial(10)});
    }
    

    相互再帰

    const std = @import("std");
    
    fn isEven(n: u32) bool {
        if (n == 0) return true;
        return isOdd(n - 1);
    }
    
    fn isOdd(n: u32) bool {
        if (n == 0) return false;
        return isEven(n - 1);
    }
    
    pub fn main() void {
        std.debug.print("5 is even: {}\n", .{isEven(5)});
        std.debug.print("6 is even: {}\n", .{isEven(6)});
    }
    

    関数ポインタ

    基本的な関数ポインタ

    const std = @import("std");
    
    fn add(a: i32, b: i32) i32 {
        return a + b;
    }
    
    fn sub(a: i32, b: i32) i32 {
        return a - b;
    }
    
    fn apply(f: *const fn(i32, i32) i32, a: i32, b: i32) i32 {
        return f(a, b);
    }
    
    pub fn main() void {
        const result1 = apply(&add, 10, 5);
        const result2 = apply(&sub, 10, 5);
    
        std.debug.print("10 + 5 = {}\n", .{result1});
        std.debug.print("10 - 5 = {}\n", .{result2});
    }
    

    コールバック関数

    const std = @import("std");
    
    fn forEach(comptime T: type, array: []const T, callback: *const fn(T) void) void {
        for (array) |item| {
            callback(item);
        }
    }
    
    fn printItem(item: i32) void {
        std.debug.print("{} ", .{item});
    }
    
    pub fn main() void {
        const numbers = [_]i32{ 1, 2, 3, 4, 5 };
        forEach(i32, &numbers, &printItem);
        std.debug.print("\n", .{});
    }
    

    高階関数

    const std = @import("std");
    
    fn map(
        comptime T: type,
        comptime R: type,
        allocator: std.mem.Allocator,
        array: []const T,
        func: *const fn(T) R,
    ) ![]R {
        const result = try allocator.alloc(R, array.len);
        for (array, 0..) |item, i| {
            result[i] = func(item);
        }
        return result;
    }
    
    fn double(x: i32) i32 {
        return x * 2;
    }
    
    pub fn main() !void {
        const allocator = std.heap.page_allocator;
        const numbers = [_]i32{ 1, 2, 3, 4, 5 };
    
        const doubled = try map(i32, i32, allocator, &numbers, &double);
        defer allocator.free(doubled);
    
        std.debug.print("Doubled: ", .{});
        for (doubled) |num| {
            std.debug.print("{} ", .{num});
        }
        std.debug.print("\n", .{});
    }
    

    インライン関数

    明示的なインライン化

    const std = @import("std");
    
    // inline キーワードで強制的にインライン化
    inline fn square(x: i32) i32 {
        return x * x;
    }
    
    pub fn main() void {
        const result = square(5);
        std.debug.print("5^2 = {}\n", .{result});
    }
    

    comptime ブロック内での関数呼び出し

    const std = @import("std");
    
    fn createLookupTable(comptime size: usize) [size]u32 {
        var table: [size]u32 = undefined;
        comptime var i = 0;
        inline while (i < size) : (i += 1) {
            table[i] = i * i;
        }
        return table;
    }
    
    pub fn main() void {
        const squares = createLookupTable(10);
        std.debug.print("Squares: ", .{});
        for (squares) |s| {
            std.debug.print("{} ", .{s});
        }
        std.debug.print("\n", .{});
    }
    

    実践例

    例1: 数学関数ライブラリ

    const std = @import("std");
    
    const MathLib = struct {
        pub fn gcd(a: u64, b: u64) u64 {
            if (b == 0) return a;
            return gcd(b, a % b);
        }
    
        pub fn lcm(a: u64, b: u64) u64 {
            return (a * b) / gcd(a, b);
        }
    
        pub fn isPrime(n: u64) bool {
            if (n < 2) return false;
            if (n == 2) return true;
            if (n % 2 == 0) return false;
    
            var i: u64 = 3;
            while (i * i <= n) : (i += 2) {
                if (n % i == 0) return false;
            }
            return true;
        }
    };
    
    pub fn main() void {
        std.debug.print("gcd(48, 18) = {}\n", .{MathLib.gcd(48, 18)});
        std.debug.print("lcm(12, 18) = {}\n", .{MathLib.lcm(12, 18)});
        std.debug.print("17 is prime: {}\n", .{MathLib.isPrime(17)});
    }
    

    例2: 配列操作ユーティリティ

    const std = @import("std");
    
    fn filter(
        comptime T: type,
        allocator: std.mem.Allocator,
        array: []const T,
        predicate: *const fn(T) bool,
    ) ![]T {
        var result = std.ArrayList(T).init(allocator);
        defer result.deinit();
    
        for (array) |item| {
            if (predicate(item)) {
                try result.append(item);
            }
        }
    
        return result.toOwnedSlice();
    }
    
    fn isEven(x: i32) bool {
        return @mod(x, 2) == 0;
    }
    
    pub fn main() !void {
        const allocator = std.heap.page_allocator;
        const numbers = [_]i32{ 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 };
    
        const evens = try filter(i32, allocator, &numbers, &isEven);
        defer allocator.free(evens);
    
        std.debug.print("Even numbers: ", .{});
        for (evens) |num| {
            std.debug.print("{} ", .{num});
        }
        std.debug.print("\n", .{});
    }
    

    まとめ

    この章では、Zigの関数について学びました:

  • 基本的な関数: 定義、パラメータ、戻り値
  • comptime関数: コンパイル時計算、ジェネリック関数
  • 再帰: 基本的な再帰、末尾再帰、相互再帰
  • 関数ポインタ: コールバック、高階関数
  • インライン関数: パフォーマンス最適化
  • 次の章では、ポインタとスライスについて学びます。

    参考資料

  • Zig Language Reference - Functions: https://ziglang.org/documentation/master/#Functions
  • Ziglearn - Functions: https://ziglearn.org/chapter-1/#functions
  • Ziglearn - Generic Types: https://ziglearn.org/chapter-2/#generic-types