オフィスアワーがそろそろ始まるよ!()

暗黙の型変換と明示的な型変換

型変換 (キャスト) とは、ある型を別の型に変換することです。Zenには暗黙の型変換明示的な型変換があります。

暗黙の型変換は、型変換が安全であることが保証されている場合に、プログラマが明示的に書かなくても行われる型変換です。明示的な型変換は、Zenの組込み関数を用いてプログラマが明示的に行う型変換です。

暗黙の型変換

ある型が求められている時に、異なる型を渡した際、暗黙の型変換が発生します。暗黙の型変換は、曖昧さがなく、その型変換が安全な場合のみ発生します。

次の場合について詳しく説明します。

よりビット数が多い数値型への変換

整数型では、変換元の整数型の全ての値が、変換後の整数型で表現可能な場合、暗黙の型変換が働きます。わかりやすい変換は、より広いビット幅をもつ整数型 (符号なし→符号なし、または、符号あり→符号あり) です。

examples/ch11-advanced/cast/src/widening_number.zen:4:18

test "integer widening" {
    var x: u8 = 250;
    // u8 => u16
    var y: u16 = x;
    ok(x == y);

    // u8 => u9
    var z: u9 = x;
    ok(x == z);

    var xi: i8 = -127;
    // i8 => i16
    var yi: i16 = xi;
    ok(xi == yi);
}

符号なしから符号ありに変換する場合、変換後の符号あり整数型は、変換元の符号なし整数型より、1ビット以上広いビット幅が必要です。次の例で変数zi9型です。i9型は-256から255の数値を表現でき、u8型が取りうる0から255を全て表現できるため、暗黙の型変換が可能です。

examples/ch11-advanced/cast/src/widening_number.zen:20:27

test "unsigned integer to signed integer" {
    var x: u8 = 255;
    var y: i16 = x;
    ok(y == 255);

    var z: i9 = x;
    ok(z == 255);
}

浮動小数点型も同様に、より広いビット幅を持つ型に変換できます。

examples/ch11-advanced/cast/src/widening_number.zen:29:34

test "float widening" {
    var x: f16 = 1234.5678;
    var y: f32 = x;
    var z: f64 = y;
    ok(x == z);
}

より制約の強い型への変換

イミュータブルな型と volatile はある型に対して、より厳しい制約を課しています。 ミュータブルなポインタ型 *mut T*T に変換する場合や volatile でない型 Tvolatile T に変換する場合は暗黙の型変換が働きます。

examples/ch11-advanced/cast/src/qualification.zen:4:10

test "const qualification" {
    var x: u32 = 42;
    const ptr: *mut u32 = &mut x;
    // *mut u32 => *u32
    const const_ptr: *u32 = ptr;
    ok(ptr == const_ptr);
}

alignはある型に対して、より小さいアライメントを指定すると制約が強くなります。そのため、より小さいアライメントを求める場合、暗黙の型変換が働きます。

examples/ch11-advanced/cast/src/qualification.zen:12:15

test "smaller alignment" {
    var x: u32 align(8) = 42;
    var y: u32 align(4) = x;
}

配列とポインタ

配列とポインタ (ファットポインタであるスライスを含む) とは、一定の条件を満たしている場合、暗黙の型変換が働きます。

配列へのポインタからスライス

配列へのポインタ (*[N]T) からスライス ([]T) へと変換する際、暗黙の型変換が働きます。

examples/ch11-advanced/cast/src/arrays_and_pointers.zen:5:13

test "pointer to an array to slice" {
    var array = [5]u8{ 1, 2, 3, 4, 5 };
    var a: *[5]u8 = &array;
    // *[5]u8 => []u8
    const s: []u8 = a;
    equalSlices(u8, s, &[_]u8{ 1, 2, 3, 4, 5 });
    // スライスの要素数は配列の要素数と同じ
    ok(s.len == a.len);
}

配列へのポインタから要素数が不明なポインタ

配列へのポインタ (*[N]T) から要素数が不明なポインタ ([*]T) へと変換する際、暗黙の型変換が働きます。

examples/ch11-advanced/cast/src/arrays_and_pointers.zen:15:20

test "pointer to an array to unknown length pointer" {
    var array = [5]u8{ 1, 2, 3, 4, 5 };
    var a: *[5]u8 = &array;
    const unknown_length: [*]u8 = a;
    ok(unknown_length[4] == 5);
}

この暗黙の型変換は、C言語との相互運用で役立ちますが、一般的にはあまり利用すべきでありません。

オプション型

オプション型 (?T) に対して、Tnullから?Tに変換する場合、暗黙の型変換が働きます。

examples/ch11-advanced/cast/src/optional.zen:4:10

test "casting to optional" {
    const x: ?u32 = 42;
    const y: ?u32 = null;

    ok(x.? == 42);
    ok(y == null);
}

エラー共用体

エラー共用体 (E!T) に対して、エラー型ETからE!Tに変換する場合、暗黙の型変換が働きます。

examples/ch11-advanced/cast/src/error_union.zen:5:16

const Error = error{
    A,
    B,
};

test "cast to error union" {
    const x: Error!u32 = 42;
    const y: Error!u32 = Error.A;

    ok((try x) == 42);
    err(Error.A, y);
}

コンパイル時計算可能な整数

コンパイル時計算可能な整数は、が変換後の整数型の範囲内である場合、暗黙の型変換が働きます。コンパイル時計算可能な整数には、コンパイル時整数 (comptime_int) と、コンパイル時計算可能な整数を代入したconst変数とが、該当します。

examples/ch11-advanced/cast/src/comptime_known.zen:4:14

test "casting comptime known integers" {
    // xは`comptime_int`型
    const x = 255;
    // comptime_int => u64
    // `y`はコンパイル時計算可能な整数
    const y: u64 = x;
    // u64 => u8
    // `y`の「値」は`255`であることがコンパイル時計算可能なため
    // 暗黙の型変換が働く
    const z: u8 = y;
}

タグ付き共用体から列挙型

タグ付き共用体を列挙型に型変換する場合、暗黙の型変換が働きます。

examples/ch11-advanced/cast/src/tagged_union.zen:4:19

const Tag = enum {
    U32,
    F64,
};

const Value = union(Tag) {
    U32: u32,
    F64: f64,
};

test "casting tagged union to enum" {
    var value = Value{ .U32 = 42 };
    // Value => Tag
    var tag: Tag = value;
    ok(tag == Tag.U32);
}

undefined

undefinedはあらゆる型に対して、暗黙の型変換が働きます。

明示的な型変換

安全な型変換

組込み関数 @to を使用することで安全な型変換が行えます。

    const value = @to(u32, 42);

@to で型変換できるのは、コンパイル時に安全と判断できる値のみです。例えば、よりビット幅の狭い整数から、よりビット幅の広い整数への変換です (u8からu32など) 。

下記のような関数形式の型変換は、0.8.0で廃止されました。

  • const value = u32(42);

組込み関数 @is を使用すると戻り値をオプション型で返し、アンラップによって安全に型変換できない場合の処理を記述することができます。

例えば、次のコードのように構造体がインタフェース制約を満たしているかどうかのチェックに使用できます。

examples/ch11-advanced/cast/src/explicit_conversion.zen:43:114

const Food = interface {
    pub fn eat() void;
};

const Drink = interface {
    pub fn drink() void;
};

const Coffee = struct {
    pub fn drink(self: *mut Coffee) void {
        std.debug.warn("drink Coffee\n", .{});
    }
};

const Pancake = struct {
    pub fn eat(self: *mut Pancake) void {
        std.debug.warn("eat Pancake\n", .{});
    }
};

const Curry = struct {
    pub fn eat(self: *mut Curry) void {
        std.debug.warn("eat Curry\n", .{});
    }

    pub fn drink(self: *mut Curry) void {
        std.debug.warn("drink Curry\n", .{});
    }
};

test "query casting interface" {
    // Coffee は Drink interface を実装しているため Drink にキャスト可能
    var coffee = Coffee{};

    // `@is` によるオプション型の戻り値をアンラップすることで interface が実装されていることを確認
    var query_coffee = @is(Drink, coffee);
    if (query_coffee) |casted| {
        casted.drink();
    } else {
        return error.InvalidCast;
    }

    // Pancake は Food interface を実装しているため Food にキャスト可能
    var pancake = Pancake{};

    // 条件式に直接 `@is` を書くことでキャストできる interface を確認することが可能
    if (@is(Drink, pancake)) |drink| {
        drink.drink();
    } else if (@is(Food, pancake)) |food| {
        food.eat();
    } else {
        return error.InvalidCast;
    }

    // Curry は Food interface と Drink interface を両方実装しているため Food, Drink それぞれにキャスト可能
    var curry = Curry{};

    // 複数の interface を実装している構造体はそれぞれの interface へのキャストを確認することが可能
    var query_curry = @is(Food, curry);
    if (query_curry) |casted| {
        casted.eat();
    } else {
        return error.InvalidCast;
    }

    query_curry = @is(Drink, curry);
    if (query_curry) |casted| {
        casted.drink();
    } else {
        return error.InvalidCast;
    }
}

安全とは限らない型変換

明示的な型変換はZen言語の組込み関数を利用して行います。明示的な型変換は、未定義動作を引き起す可能性があります。未定義動作を引き起こす条件と、対策方法は11.1 未定義動作を参照して下さい。

いくつかの明示的な型変換に対しては、言語レベルのチェックが入っています。例えば、@intCastは引数の型に整数型しか受け付けません。

組込み関数 説明
@bitCast ビット表現を変更せずに型を変換します
@alignCast ポインタのアライメントを変換します
@boolToInt trueを1に、falseを0に、変換します
@bytesToSlice u8のスライスを別の要素型のスライスに変換します
@enumToInt 列挙型もしくはタグ付き共用体からタグの整数値を取得します
@errSetCast エラー型をサブセットのエラー型に変換します
@errorToInt エラー種別の整数値を取得します
@floatCast よりビット幅の少ない浮動小数点型に変換します
@floatToInt 浮動小数点値の整数部分を取得します
@intCast 整数型同士を変換します
@intToEnum 整数値から列挙型の値を取得します
@intToError 整数値からエラー種別を取得します
@intToFloat 整数値を浮動小数点値に変換します
@intToPtr アドレスをポインタに変換します
@ptrCast ポインタ型同士を変換します
@ptrToInt ポインタからアドレスに変換します
@sliceToBytes 何らかの要素型のスライスからu8のスライスへ変換します
@truncate 整数型同士の変換を行い、上位ビットを切り捨てます

Chapter 1

Chapter 2

Chapter 3

Chapter 4

Chapter 5

Chapter 6

Chapter 7

Chapter 8

Chapter 9

Chapter 10

Chapter 11

Chapter 12

Chapter 13

Chapter 14

Chapter 15

Appendix

Error Explanation

☰ 人の生きた証は永遠に残るよう ☰
Copyright © 2018-2020 connectFree Corporation. All rights reserved. | 特定商取引法に基づく表示
Zen, the Zen three-circles logo and The Zen Programming Language are trademarks of connectFree corporation in Japan and other countries.