第4章: 型システム
学習目標
この章を終えると、以下ができるようになります:
- Zigの型システムの特徴を理解する
- 整数型と浮動小数点型を適切に選択できる
- オプショナル型を使ってnull安全なコードを書ける
- 型の変換とキャストを正しく行える
Zigの型システムの特徴
静的型付け
Zigは静的型付け言語であり、すべての型はコンパイル時に決定されます。
const std = @import("std");
pub fn example() void {
// 型は明示的に指定するか、推論される
const x: i32 = 42; // 明示的
const y = 42; // 推論(i32)
const z = 3.14; // 推論(f64)
// 型の不一致はコンパイルエラー
// var a: i32 = "string"; // エラー!
std.debug.print("x={}, y={}, z={d:.2}\n", .{x, y, z});
}
型の第一級市民
Zigでは、型自体が値として扱われます。
const std = @import("std");
pub fn example() void {
// 型を変数に代入
const MyType = i32;
const x: MyType = 42;
// 型を関数に渡す
printType(i32);
printType(f64);
printType(bool);
}
fn printType(comptime T: type) void {
std.debug.print("Type: {}\n", .{T});
}
null安全性
Zigは、nullポインタ参照を型システムで防ぎます。
const std = @import("std");
pub fn example() void {
// 通常の値はnullになれない
var x: i32 = 42;
// x = null; // コンパイルエラー!
// nullを許容するにはオプショナル型を使う
var y: ?i32 = 42;
y = null; // OK
std.debug.print("x={}, y={?}\n", .{x, y});
}
整数型の詳細
符号付き整数型
Zigは、さまざまなサイズの符号付き整数型を提供します。
const std = @import("std");
pub fn example() void {
// 標準的な符号付き整数型
const i8_val: i8 = -128;
const i16_val: i16 = -32768;
const i32_val: i32 = -2147483648;
const i64_val: i64 = -9223372036854775808;
const i128_val: i128 = -1000000000000000000;
// isize: ポインタサイズの符号付き整数
const isize_val: isize = -100;
std.debug.print("i8: {}\n", .{i8_val});
std.debug.print("i16: {}\n", .{i16_val});
std.debug.print("i32: {}\n", .{i32_val});
std.debug.print("i64: {}\n", .{i64_val});
std.debug.print("i128: {}\n", .{i128_val});
std.debug.print("isize: {}\n", .{isize_val});
}
型の範囲:
型 ビット数 最小値 最大値
------ -------- ------------------------------ ------------------------------
i8 8 -128 127
i16 16 -32,768 32,767
i32 32 -2,147,483,648 2,147,483,647
i64 64 -9,223,372,036,854,775,808 9,223,372,036,854,775,807
i128 128 -(2^127) 2^127 - 1
符号なし整数型
const std = @import("std");
pub fn example() void {
// 標準的な符号なし整数型
const u8_val: u8 = 255;
const u16_val: u16 = 65535;
const u32_val: u32 = 4294967295;
const u64_val: u64 = 18446744073709551615;
// usize: ポインタサイズの符号なし整数
const usize_val: usize = 100;
std.debug.print("u8: {}\n", .{u8_val});
std.debug.print("u16: {}\n", .{u16_val});
std.debug.print("u32: {}\n", .{u32_val});
std.debug.print("u64: {}\n", .{u64_val});
std.debug.print("usize: {}\n", .{usize_val});
}
任意ビット幅整数
Zigの特徴的な機能の一つは、任意のビット幅を持つ整数型です。
const std = @import("std");
pub fn example() void {
// 1ビットから128ビットまで、任意のビット幅を指定可能
const u1_val: u1 = 1; // 0または1
const u2_val: u2 = 3; // 0-3
const u3_val: u3 = 7; // 0-7
const u5_val: u5 = 31; // 0-31
const u7_val: u7 = 127; // 0-127
// 符号付きも可能
const i3_val: i3 = -4; // -4 to 3
const i5_val: i5 = -16; // -16 to 15
std.debug.print("u1: {}\n", .{u1_val});
std.debug.print("u2: {}\n", .{u2_val});
std.debug.print("u3: {}\n", .{u3_val});
std.debug.print("u5: {}\n", .{u5_val});
std.debug.print("u7: {}\n", .{u7_val});
std.debug.print("i3: {}\n", .{i3_val});
std.debug.print("i5: {}\n", .{i5_val});
}
活用例: ビットフィールド
const std = @import("std");
const Flags = packed struct {
read: u1, // 1ビット
write: u1, // 1ビット
execute: u1, // 1ビット
reserved: u5, // 5ビット(予約)
};
pub fn example() void {
var flags = Flags{
.read = 1,
.write = 1,
.execute = 0,
.reserved = 0,
};
std.debug.print("Can read: {}\n", .{flags.read});
std.debug.print("Can write: {}\n", .{flags.write});
std.debug.print("Can execute: {}\n", .{flags.execute});
std.debug.print("Size: {} bytes\n", .{@sizeOf(Flags)});
}
整数オーバーフロー
Zigは、整数オーバーフローを検出し、適切に処理します。
const std = @import("std");
pub fn example() void {
// デバッグビルドではオーバーフローでパニック
var x: u8 = 255;
// x += 1; // パニック!
// ラップアラウンド演算(+%)
var y: u8 = 255;
y +%= 1; // 0になる
std.debug.print("Wrapping: 255 +%%= 1 = {}\n", .{y});
// 飽和演算(+|)
var z: u8 = 255;
z +|= 1; // 255のまま
std.debug.print("Saturating: 255 +|= 1 = {}\n", .{z});
// チェック付き演算
const result = @addWithOverflow(u8, 255, 1);
std.debug.print("Overflow: {}, Result: {}\n", .{result[1], result[0]});
}
演算子の種類:
演算子 動作
------ ----
+ デバッグビルドでオーバーフロー検出
+% ラップアラウンド(常に)
+| 飽和演算(常に)
- デバッグビルドでアンダーフロー検出
-% ラップアラウンド(常に)
-| 飽和演算(常に)
* デバッグビルドでオーバーフロー検出
*% ラップアラウンド(常に)
*| 飽和演算(常に)
浮動小数点型
基本的な浮動小数点型
const std = @import("std");
pub fn example() void {
// IEEE 754準拠の浮動小数点型
const f16_val: f16 = 3.14;
const f32_val: f32 = 3.14159;
const f64_val: f64 = 3.141592653589793;
const f128_val: f128 = 3.141592653589793238462643383279502884;
std.debug.print("f16: {d:.2}\n", .{f16_val});
std.debug.print("f32: {d:.5}\n", .{f32_val});
std.debug.print("f64: {d:.15}\n", .{f64_val});
std.debug.print("f128: {d:.30}\n", .{f128_val});
}
型の精度:
型 ビット数 仮数部 指数部 精度(10進数)
------ -------- ------- ------- -------------
f16 16 10 5 約3桁
f32 32 23 8 約7桁
f64 64 52 11 約15桁
f128 128 112 15 約33桁
特殊な浮動小数点値
const std = @import("std");
pub fn example() void {
// 無限大
const inf = std.math.inf(f64);
const neg_inf = -std.math.inf(f64);
// NaN(Not a Number)
const nan = std.math.nan(f64);
// ゼロ
const zero: f64 = 0.0;
const neg_zero: f64 = -0.0;
std.debug.print("inf: {d}\n", .{inf});
std.debug.print("neg_inf: {d}\n", .{neg_inf});
std.debug.print("nan: {d}\n", .{nan});
std.debug.print("zero: {d}\n", .{zero});
std.debug.print("neg_zero: {d}\n", .{neg_zero});
// 無限大やNaNのチェック
std.debug.print("inf is infinite: {}\n", .{std.math.isInf(inf)});
std.debug.print("nan is nan: {}\n", .{std.math.isNan(nan)});
}
浮動小数点演算
const std = @import("std");
pub fn example() void {
const a: f64 = 10.5;
const b: f64 = 3.2;
// 基本演算
const sum = a + b;
const diff = a - b;
const prod = a * b;
const quot = a / b;
std.debug.print("sum: {d:.2}\n", .{sum});
std.debug.print("diff: {d:.2}\n", .{diff});
std.debug.print("prod: {d:.2}\n", .{prod});
std.debug.print("quot: {d:.2}\n", .{quot});
// 数学関数
const sqrt_val = std.math.sqrt(a);
const pow_val = std.math.pow(f64, a, 2.0);
const sin_val = std.math.sin(a);
std.debug.print("sqrt: {d:.2}\n", .{sqrt_val});
std.debug.print("pow: {d:.2}\n", .{pow_val});
std.debug.print("sin: {d:.2}\n", .{sin_val});
}
オプショナル型
基本的なオプショナル型
オプショナル型は、値が存在しない可能性を表現します。
const std = @import("std");
pub fn example() void {
// オプショナル型の宣言
var maybe_number: ?i32 = 42;
// nullを代入
maybe_number = null;
// 値を再代入
maybe_number = 100;
std.debug.print("maybe_number: {?}\n", .{maybe_number});
}
オプショナル型のアンラップ
const std = @import("std");
pub fn example() void {
const maybe_number: ?i32 = 42;
// if文でアンラップ
if (maybe_number) |value| {
std.debug.print("Value exists: {}\n", .{value});
} else {
std.debug.print("Value is null\n", .{});
}
// orelseでデフォルト値を提供
const number = maybe_number orelse 0;
std.debug.print("Number: {}\n", .{number});
}
オプショナル型を返す関数
const std = @import("std");
fn findNumber(arr: []const i32, target: i32) ?usize {
for (arr, 0..) |value, index| {
if (value == target) {
return index;
}
}
return null; // 見つからなかった
}
pub fn example() void {
const numbers = [_]i32{ 1, 2, 3, 4, 5 };
if (findNumber(&numbers, 3)) |index| {
std.debug.print("Found at index: {}\n", .{index});
} else {
std.debug.print("Not found\n", .{});
}
if (findNumber(&numbers, 10)) |index| {
std.debug.print("Found at index: {}\n", .{index});
} else {
std.debug.print("Not found\n", .{});
}
}
オプショナルポインタ
const std = @import("std");
pub fn example() void {
var x: i32 = 42;
// オプショナルポインタ
var maybe_ptr: ?*i32 = &x;
if (maybe_ptr) |ptr| {
std.debug.print("Value: {}\n", .{ptr.*});
ptr.* = 100; // 値を変更
}
std.debug.print("x: {}\n", .{x});
// nullを代入
maybe_ptr = null;
if (maybe_ptr) |ptr| {
std.debug.print("Value: {}\n", .{ptr.*});
} else {
std.debug.print("Pointer is null\n", .{});
}
}
型変換とキャスト
明示的な型変換
const std = @import("std");
pub fn example() void {
// 整数型の変換
const x: i16 = 100;
const y: i32 = @intCast(x);
// 浮動小数点型の変換
const f: f32 = 3.14;
const d: f64 = @floatCast(f);
// 整数から浮動小数点
const i: i32 = 42;
const float_val: f64 = @floatFromInt(i);
// 浮動小数点から整数
const pi: f64 = 3.14159;
const int_val: i32 = @intFromFloat(pi);
std.debug.print("y: {}\n", .{y});
std.debug.print("d: {d:.2}\n", .{d});
std.debug.print("float_val: {d:.1}\n", .{float_val});
std.debug.print("int_val: {}\n", .{int_val});
}
安全な型変換
const std = @import("std");
fn safeIntCast(comptime T: type, value: i32) ?T {
const type_info = @typeInfo(T);
if (type_info != .Int) return null;
const max = std.math.maxInt(T);
const min = std.math.minInt(T);
if (value > max or value < min) {
return null;
}
return @intCast(value);
}
pub fn example() void {
const value: i32 = 100;
if (safeIntCast(i8, value)) |result| {
std.debug.print("Success: {}\n", .{result});
} else {
std.debug.print("Failed to cast\n", .{});
}
const large_value: i32 = 1000;
if (safeIntCast(i8, large_value)) |result| {
std.debug.print("Success: {}\n", .{result});
} else {
std.debug.print("Failed to cast (out of range)\n", .{});
}
}
ビットキャスト
const std = @import("std");
pub fn example() void {
// ビットパターンを維持したまま型を変換
const x: u32 = 0x3f800000;
const f: f32 = @bitCast(x); // 1.0
std.debug.print("u32 0x{x} as f32: {d:.1}\n", .{x, f});
// 逆変換
const float_val: f32 = 1.5;
const int_val: u32 = @bitCast(float_val);
std.debug.print("f32 {d:.1} as u32: 0x{x}\n", .{float_val, int_val});
}
型情報の取得
@TypeOf
const std = @import("std");
pub fn example() void {
const x = 42;
const y = 3.14;
const z = true;
const T1 = @TypeOf(x);
const T2 = @TypeOf(y);
const T3 = @TypeOf(z);
std.debug.print("Type of x: {}\n", .{T1});
std.debug.print("Type of y: {}\n", .{T2});
std.debug.print("Type of z: {}\n", .{T3});
}
@typeInfo
const std = @import("std");
pub fn example() void {
const type_info = @typeInfo(u8);
std.debug.print("Type: {}\n", .{type_info});
if (type_info == .Int) {
std.debug.print("Bits: {}\n", .{type_info.Int.bits});
std.debug.print("Signedness: {}\n", .{type_info.Int.signedness});
}
}
@sizeOf と @alignOf
const std = @import("std");
pub fn example() void {
// 型のサイズ(バイト)
std.debug.print("size of u8: {}\n", .{@sizeOf(u8)});
std.debug.print("size of i32: {}\n", .{@sizeOf(i32)});
std.debug.print("size of f64: {}\n", .{@sizeOf(f64)});
// 型のアライメント(バイト)
std.debug.print("align of u8: {}\n", .{@alignOf(u8)});
std.debug.print("align of i32: {}\n", .{@alignOf(i32)});
std.debug.print("align of f64: {}\n", .{@alignOf(f64)});
}
まとめ
この章では、Zigの型システムについて学びました:
次の章では、制御構造(if、while、for、switch)について学びます。