第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の型システムについて学びました:

  • 整数型: 符号付き/符号なし、任意ビット幅、オーバーフロー処理
  • 浮動小数点型: IEEE 754準拠、特殊な値、数学関数
  • オプショナル型: null安全、アンラップ、オプショナルポインタ
  • 型変換: 明示的な変換、安全な変換、ビットキャスト
  • 型情報: @TypeOf、@typeInfo、@sizeOf、@alignOf
  • 次の章では、制御構造(if、while、for、switch)について学びます。

    参考資料

  • Zig Language Reference - Types: https://ziglang.org/documentation/master/#Types
  • Zig Standard Library - Math: https://ziglang.org/documentation/master/std/#std.math
  • Ziglearn - Advanced Types: https://ziglearn.org/chapter-2/