第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の関数について学びました:
次の章では、ポインタとスライスについて学びます。