第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のコンパイル時計算について学びました:
次の章では、メモリ管理について学びます。