第11章: コンパイル時計算

学習目標

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

  • comptime変数と関数を理解し、使用できる
  • コンパイル時に型を生成できる
  • メタプログラミングでコードを自動生成できる
  • comptimeを使ってパフォーマンスを最適化できる
  • comptimeの基本

    comptime変数

    comptimeキーワードを使うと、変数がコンパイル時に評価されることを保証できます。

    const std = @import("std");
    
    pub fn example() void {
        // コンパイル時定数
        comptime var x = 10;
        comptime var y = 20;
        comptime var sum = x + y;
    
        std.debug.print("Sum: {}\n", .{sum});
    
        // コンパイル時に配列のサイズを計算
        comptime var size = 5;
        var array: [size]i32 = undefined;
    
        for (&array, 0..) |*item, i| {
            item.* = @intCast(i * 2);
        }
    
        std.debug.print("Array: ", .{});
        for (array) |item| {
            std.debug.print("{} ", .{item});
        }
        std.debug.print("\n", .{});
    }
    

    comptime関数

    コンパイル時に実行される関数を定義できます。

    const std = @import("std");
    
    fn fibonacci(n: u32) u32 {
        if (n <= 1) return n;
        return fibonacci(n - 1) + fibonacci(n - 2);
    }
    
    pub fn example() void {
        // コンパイル時にフィボナッチ数を計算
        comptime var fib10 = fibonacci(10);
        std.debug.print("Fibonacci(10) = {}\n", .{fib10});
    
        // 配列のサイズとしても使える
        var fibs: [fib10]u32 = undefined;
        std.debug.print("Array size: {}\n", .{fibs.len});
    }
    

    comptime パラメータ

    関数のパラメータをcomptimeにすると、型パラメータとして使用できます。

    const std = @import("std");
    
    fn printType(comptime T: type) void {
        std.debug.print("Type: {}\n", .{T});
        std.debug.print("Size: {} bytes\n", .{@sizeOf(T)});
        std.debug.print("Alignment: {} bytes\n", .{@alignOf(T)});
    }
    
    fn createArray(comptime T: type, comptime size: usize) [size]T {
        var array: [size]T = undefined;
        for (&array, 0..) |*item, i| {
            if (@typeInfo(T) == .Int) {
                item.* = @intCast(i);
            }
        }
        return array;
    }
    
    pub fn example() void {
        std.debug.print("=== Type Information ===\n", .{});
        printType(i32);
        printType(f64);
    
        std.debug.print("\n=== Array Creation ===\n", .{});
        const int_array = createArray(i32, 5);
        std.debug.print("Int array: ", .{});
        for (int_array) |item| {
            std.debug.print("{} ", .{item});
        }
        std.debug.print("\n", .{});
    }
    

    型の生成

    ジェネリック型の作成

    const std = @import("std");
    
    fn Vec2(comptime T: type) type {
        return struct {
            x: T,
            y: T,
    
            const Self = @This();
    
            pub fn init(x: T, y: T) Self {
                return Self{ .x = x, .y = y };
            }
    
            pub fn add(self: Self, other: Self) Self {
                return Self{
                    .x = self.x + other.x,
                    .y = self.y + other.y,
                };
            }
    
            pub fn dot(self: Self, other: Self) T {
                return self.x * other.x + self.y * other.y;
            }
    
            pub fn magnitude(self: Self) T {
                if (@typeInfo(T) == .Float) {
                    return std.math.sqrt(self.x * self.x + self.y * self.y);
                }
                @compileError("magnitude requires float type");
            }
        };
    }
    
    pub fn example() void {
        const Vec2i = Vec2(i32);
        const Vec2f = Vec2(f64);
    
        const v1 = Vec2i.init(3, 4);
        const v2 = Vec2i.init(1, 2);
        const sum = v1.add(v2);
    
        std.debug.print("Vec2i: ({}, {})\n", .{sum.x, sum.y});
        std.debug.print("Dot product: {}\n", .{v1.dot(v2)});
    
        const v3 = Vec2f.init(3.0, 4.0);
        std.debug.print("Magnitude: {d:.2}\n", .{v3.magnitude()});
    }
    

    条件付き型生成

    const std = @import("std");
    
    fn SmartInt(comptime bits: u16) type {
        if (bits <= 8) {
            return u8;
        } else if (bits <= 16) {
            return u16;
        } else if (bits <= 32) {
            return u32;
        } else {
            return u64;
        }
    }
    
    fn Storage(comptime T: type, comptime is_heap: bool) type {
        if (is_heap) {
            return struct {
                data: *T,
                allocator: std.mem.Allocator,
    
                pub fn init(allocator: std.mem.Allocator, value: T) !@This() {
                    const data = try allocator.create(T);
                    data.* = value;
                    return .{ .data = data, .allocator = allocator };
                }
    
                pub fn deinit(self: @This()) void {
                    self.allocator.destroy(self.data);
                }
    
                pub fn get(self: @This()) T {
                    return self.data.*;
                }
            };
        } else {
            return struct {
                data: T,
    
                pub fn init(allocator: std.mem.Allocator, value: T) !@This() {
                    _ = allocator;
                    return .{ .data = value };
                }
    
                pub fn deinit(self: @This()) void {
                    _ = self;
                }
    
                pub fn get(self: @This()) T {
                    return self.data;
                }
            };
        }
    }
    
    pub fn example() !void {
        // 最適なサイズの型を自動選択
        const Small = SmartInt(7);
        const Medium = SmartInt(12);
        const Large = SmartInt(25);
    
        std.debug.print("Small: {} bytes\n", .{@sizeOf(Small)});
        std.debug.print("Medium: {} bytes\n", .{@sizeOf(Medium)});
        std.debug.print("Large: {} bytes\n", .{@sizeOf(Large)});
    
        // スタックまたはヒープを選択
        const allocator = std.heap.page_allocator;
    
        var stack_storage = try Storage(i32, false).init(allocator, 42);
        defer stack_storage.deinit();
        std.debug.print("\nStack value: {}\n", .{stack_storage.get()});
    
        var heap_storage = try Storage(i32, true).init(allocator, 100);
        defer heap_storage.deinit();
        std.debug.print("Heap value: {}\n", .{heap_storage.get()});
    }
    

    コンパイル時リフレクション

    型情報の取得

    const std = @import("std");
    
    const Person = struct {
        name: []const u8,
        age: u32,
        email: []const u8,
    };
    
    fn printFields(comptime T: type) void {
        const fields = std.meta.fields(T);
        std.debug.print("Type {} has {} fields:\n", .{T, fields.len});
    
        inline for (fields) |field| {
            std.debug.print("  - {s}: {}\n", .{field.name, field.type});
        }
    }
    
    fn hasField(comptime T: type, comptime field_name: []const u8) bool {
        const fields = std.meta.fields(T);
        inline for (fields) |field| {
            if (std.mem.eql(u8, field.name, field_name)) {
                return true;
            }
        }
        return false;
    }
    
    pub fn example() void {
        printFields(Person);
    
        std.debug.print("\nHas 'name' field: {}\n", .{hasField(Person, "name")});
        std.debug.print("Has 'address' field: {}\n", .{hasField(Person, "address")});
    }
    

    列挙型のリフレクション

    const std = @import("std");
    
    const Color = enum {
        Red,
        Green,
        Blue,
        Yellow,
    };
    
    fn enumToString(value: anytype) []const u8 {
        const T = @TypeOf(value);
        const info = @typeInfo(T);
    
        if (info != .Enum) {
            @compileError("enumToString requires enum type");
        }
    
        inline for (info.Enum.fields) |field| {
            if (@intFromEnum(value) == field.value) {
                return field.name;
            }
        }
        return "Unknown";
    }
    
    fn printEnumValues(comptime T: type) void {
        const info = @typeInfo(T);
        if (info != .Enum) {
            @compileError("printEnumValues requires enum type");
        }
    
        std.debug.print("Enum {} values:\n", .{T});
        inline for (info.Enum.fields) |field| {
            std.debug.print("  - {s} = {}\n", .{field.name, field.value});
        }
    }
    
    pub fn example() void {
        const color = Color.Red;
        std.debug.print("Color: {s}\n\n", .{enumToString(color)});
    
        printEnumValues(Color);
    }
    

    メタプログラミングパターン

    コンパイル時ルックアップテーブル

    const std = @import("std");
    
    fn generateSineTable(comptime size: usize) [size]f64 {
        var table: [size]f64 = undefined;
        for (&table, 0..) |*item, i| {
            const angle = 2.0 * std.math.pi * @as(f64, @floatFromInt(i)) / @as(f64, @floatFromInt(size));
            item.* = @sin(angle);
        }
        return table;
    }
    
    fn generatePrimes(comptime max: u32) []const u32 {
        comptime {
            var primes: [max]u32 = undefined;
            var count: usize = 0;
            var n: u32 = 2;
    
            while (n <= max) : (n += 1) {
                var is_prime = true;
                var i: usize = 0;
                while (i < count) : (i += 1) {
                    if (n % primes[i] == 0) {
                        is_prime = false;
                        break;
                    }
                }
                if (is_prime) {
                    primes[count] = n;
                    count += 1;
                }
            }
    
            return primes[0..count];
        }
    }
    
    pub fn example() void {
        // コンパイル時にサインテーブルを生成
        comptime var sine_table = generateSineTable(8);
    
        std.debug.print("Sine table:\n", .{});
        for (sine_table, 0..) |value, i| {
            std.debug.print("  sin({}π/4) = {d:.4}\n", .{i, value});
        }
    
        // コンパイル時に素数を生成
        comptime var primes = generatePrimes(100);
    
        std.debug.print("\nFirst {} primes:\n", .{primes.len});
        for (primes) |prime| {
            std.debug.print("{} ", .{prime});
        }
        std.debug.print("\n", .{});
    }
    

    ビルダーパターン

    const std = @import("std");
    
    fn Builder(comptime T: type) type {
        return struct {
            const Self = @This();
    
            value: T,
    
            pub fn init() Self {
                return Self{ .value = undefined };
            }
    
            pub fn set(self: Self, comptime field_name: []const u8, field_value: anytype) Self {
                var new_value = self.value;
                @field(new_value, field_name) = field_value;
                return Self{ .value = new_value };
            }
    
            pub fn build(self: Self) T {
                return self.value;
            }
        };
    }
    
    const Config = struct {
        host: []const u8,
        port: u16,
        timeout: u32,
        debug: bool,
    };
    
    pub fn example() void {
        const config = Builder(Config).init()
            .set("host", "localhost")
            .set("port", 8080)
            .set("timeout", 5000)
            .set("debug", true)
            .build();
    
        std.debug.print("Config:\n", .{});
        std.debug.print("  Host: {s}\n", .{config.host});
        std.debug.print("  Port: {}\n", .{config.port});
        std.debug.print("  Timeout: {}\n", .{config.timeout});
        std.debug.print("  Debug: {}\n", .{config.debug});
    }
    

    コンパイル時アサーション

    const std = @import("std");
    
    fn assertSize(comptime T: type, comptime expected_size: usize) void {
        if (@sizeOf(T) != expected_size) {
            @compileError(std.fmt.comptimePrint(
                "Size mismatch: expected {} bytes, got {} bytes",
                .{expected_size, @sizeOf(T)}
            ));
        }
    }
    
    fn assertAlignment(comptime T: type, comptime expected_align: usize) void {
        if (@alignOf(T) != expected_align) {
            @compileError(std.fmt.comptimePrint(
                "Alignment mismatch: expected {}, got {}",
                .{expected_align, @alignOf(T)}
            ));
        }
    }
    
    const Header = packed struct {
        magic: u32,
        version: u16,
        flags: u16,
    };
    
    pub fn example() void {
        // コンパイル時にサイズとアライメントをチェック
        comptime {
            assertSize(Header, 8);
            assertAlignment(u64, 8);
        }
    
        std.debug.print("Header size verified: {} bytes\n", .{@sizeOf(Header)});
        std.debug.print("u64 alignment verified: {} bytes\n", .{@alignOf(u64)});
    }
    

    高度なcomptimeテクニック

    型変換の自動化

    const std = @import("std");
    
    fn AutoConvert(comptime From: type, comptime To: type) type {
        return struct {
            pub fn convert(value: From) To {
                const from_info = @typeInfo(From);
                const to_info = @typeInfo(To);
    
                if (from_info == .Int and to_info == .Int) {
                    return @intCast(value);
                } else if (from_info == .Float and to_info == .Float) {
                    return @floatCast(value);
                } else if (from_info == .Int and to_info == .Float) {
                    return @floatFromInt(value);
                } else if (from_info == .Float and to_info == .Int) {
                    return @intFromFloat(value);
                } else {
                    @compileError("Unsupported conversion");
                }
            }
        };
    }
    
    pub fn example() void {
        const i32_to_i16 = AutoConvert(i32, i16).convert(100);
        const i32_to_f64 = AutoConvert(i32, f64).convert(42);
        const f64_to_i32 = AutoConvert(f64, i32).convert(3.14);
    
        std.debug.print("i32 -> i16: {}\n", .{i32_to_i16});
        std.debug.print("i32 -> f64: {d:.1}\n", .{i32_to_f64});
        std.debug.print("f64 -> i32: {}\n", .{f64_to_i32});
    }
    

    インライン展開の最適化

    const std = @import("std");
    
    fn unrollLoop(comptime count: usize, comptime func: fn(usize) void) void {
        inline for (0..count) |i| {
            func(i);
        }
    }
    
    fn processItem(index: usize) void {
        std.debug.print("Processing item {}\n", .{index});
    }
    
    pub fn example() void {
        std.debug.print("Unrolled loop:\n", .{});
        unrollLoop(5, processItem);
    }
    

    コンパイル時文字列処理

    const std = @import("std");
    
    fn toUpperCase(comptime str: []const u8) *const [str.len]u8 {
        comptime {
            var upper: [str.len]u8 = undefined;
            for (str, 0..) |c, i| {
                upper[i] = if (c >= 'a' and c <= 'z') c - 32 else c;
            }
            return &upper;
        }
    }
    
    fn reverse(comptime str: []const u8) *const [str.len]u8 {
        comptime {
            var rev: [str.len]u8 = undefined;
            for (str, 0..) |c, i| {
                rev[str.len - 1 - i] = c;
            }
            return &rev;
        }
    }
    
    pub fn example() void {
        comptime var hello_upper = toUpperCase("hello");
        comptime var hello_reversed = reverse("hello");
    
        std.debug.print("Upper: {s}\n", .{hello_upper});
        std.debug.print("Reversed: {s}\n", .{hello_reversed});
    }
    

    まとめ

    この章では、Zigのコンパイル時計算について学びました:

  • comptime変数と関数: コンパイル時の評価
  • 型の生成: ジェネリックプログラミング
  • リフレクション: 型情報の取得と活用
  • メタプログラミング: コード生成と最適化
  • 高度なテクニック: 自動変換、ループ展開、文字列処理
  • 次の章では、メモリ管理について学びます。

    参考資料

  • Zig Language Reference - Comptime: https://ziglang.org/documentation/master/#comptime
  • Ziglearn - Comptime: https://ziglearn.org/chapter-2/#comptime
  • Zig Standard Library - Meta: https://ziglang.org/documentation/master/std/#std.meta