ざっくりZig - キャスト(明示的な型変換)

2024/07/12に公開

Zigでは変数や引数に型を設定しますが、状況によって型を変換したいときがあります。そのため、キャスト(Cast:明示的な型変換)を行う方法が用意されています。大きく分けると代入によるものとビルトイン関数によるものです。

型が明示されない変数

キャストについて紹介する前に、constで型を明示せず値が代入された変数がどんな型なのかを確認しておきます(constでは型名を宣言することもできます)。整数型はcomptime_int、浮動小数点型はcomptime_floatです。文字列は*const [長さ:0]u8、タプルはstruct{要素の型名 = 値, ...}となります。

型が明示されない変数
const stdout = std.io.getStdOut().writer();

const i = -1;
const g = 1.0;
const s = "string";
const t = .{ -1, 1.0, "string" };
try stdout.print("@TypeOf(i) = {}\n@TypeOf(g) = {}\n@TypeOf(s) = {}\n@TypeOf(t) = {}\n", .{ @TypeOf(i), @TypeOf(g), @TypeOf(s), @TypeOf(t) });
結果
@TypeOf(i) = comptime_int
@TypeOf(g) = comptime_float
@TypeOf(s) = *const [6:0]u8
@TypeOf(t) = struct{comptime comptime_int = -1, comptime comptime_float = 1, comptime *const [6:0]u8 = "string"}

なお、キャストされる値がconstで宣言されている場合は@constCast(キャスト処理)を実行しなければならない場合があります。

数値型のキャスト

ある値が型が宣言された変数に格納されているとき、それを別の型が宣言された変数に代入することでキャストが可能な場合があります。

整数型

整数型はu8からu16のように種別(ui)が同じでビット幅を広げるだけであれば代入によりキャストできます。浮動小数点型もf16からf32などにキャストする場合は同じです。代入する際の式に含まれるのが定数とcomptime_intcomptime_floatのみのときは、計算の結果が代入されます。

数値の代入によるキャスト
var j1: u8 = 256 + i;   // i = -1 (comptime_int) 256は代入できないが式の結果の255は代入できる
var j2: u16 = j1;   // ビット幅が広がる(ビルトイン関数@intCastも可)

var g1: f16 = 12345.6789 - g;   // g = 1.0 (comptime_float)
var g2: f32 = g1;   // ビット幅が広がる

整数型のビット幅を狭める場合はビルトイン関数@trucateを実行します。これにより上位ビットが切り捨てられます。

@truncateで上位ビット切り捨て
var jp1: u16 = 0xff00;      // 65280
var jp2: u8 = @truncate(jp1);
try stdout.print("jp1 = {}, jp2 = {}\n", .{ jp1, jp2 });
jp1 = 0x00ff;               // 255
jp2 = @truncate(jp1);
try stdout.print("jp1 = {}, jp2 = {}\n", .{ jp1, jp2 });

var jm1: i16 = -0x8000;     // -32768
var jm2: i8 = @truncate(jm1);
try stdout.print("jm1 = {}, jm2 = {}\n", .{ jm1, jm2 });
jm1 = -0x0080;              // -128
jm2 = @truncate(jm1);
try stdout.print("jm1 = {}, jm2 = {}\n", .{ jm1, jm2 });
結果
jp1 = 65280, jp2 = 0
jp1 = 255, jp2 = 255
jm1 = -32768, jm2 = 0
jm1 = -128, jm2 = -128

浮動小数点型

浮動小数点型では、同じくビルトイン関数の@floatCastを実行できます。こちらはビット幅を問いません。ただし、ビット幅を狭めたときの結果がinf(infinity:無限、あるいは範囲外)となることがあります。

@floatCastによるキャスト
var g1: f16 = std.math.floatMax(f16);   // f16で扱える最大の数
var g2: f32 = g1;
try stdout.print("g1 = {}, g2 = {}\n", .{ g1, g2 });
g2 = std.math.floatMax(f32);            // f32で扱える最大の数
g1 = @floatCast(g2);
try stdout.print("g1 = {}, g2 = {}\n", .{ g1, g2 });
g1 = std.math.floatMin(f16);            // f16で扱える最小の数
g2 = @floatCast(g1);
try stdout.print("g1 = {}, g2 = {}\n", .{ g1, g2 });
g2 = std.math.floatMin(f32);            // f32で扱える最小の数
g1 = @floatCast(g2);
try stdout.print("g1 = {}, g2 = {}\n", .{ g1, g2 });
結果
g1 = 6.55e4, g2 = 6.5504e4
g1 = inf, g2 = 3.4028235e38
g1 = 6.104e-5, g2 = 6.1035156e-5
g1 = 0e0, g2 = 1.1754944e-38

整数型と浮動小数点型とでキャスト

整数型と浮動小数点型とでキャストを行う場合は以下のビルトイン関数があります。

  • 整数型 → 浮動小数点型 : @floatFromInt
  • 浮動小数点型 → 整数型 : @intFromFloat

ただし整数型と浮動小数点型とでは同じビット幅でも表せる値の範囲が異なるため、特に浮動小数点型から整数型にキャストするときにビット幅を超えてしまってpanic: integer part of floating point value out of boundsというエラーになることがあります(デバッグモード-O Debugのとき)。

整数型と浮動小数点型とでキャスト
var jn1: i16 = std.math.maxInt(i16);
g1 = @floatFromInt(j1);
try stdout.print("jn1 = {}, g1 = {}\n", .{ jn1, g1 });
g2 = std.math.floatMax(f32);
var jn2: i32 = @intFromFloat(g2);   // キャスト不可
try stdout.print("jn2 = {}, g2 = {}\n", .{ jn2, g2 });
結果
jn1 = 32767, g1 = 2.55e2
thread .... panic: integer part of floating point value out of bounds

各ビットの状態を変えずにf32からi32あるいはi32からu32などのキャストを行う場合は@bitCast関数を実行します。

f32からi32へ同じビット幅でキャスト
g2 = std.math.floatMax(f32);
var jn2: i32 = @bitCast(g2);
try stdout.print("jn2 = {}, g2 = {}\n", .{ jn2, g2 });
jn2 = -jn2;
var jn3: u32 = @bitCast(jn2);
try stdout.print("jn3 = {}, jn2 = {}\n", .{ jn3, jn2 });
結果
jn2 = 2139095039, g2 = 3.4028235e38
jn3 = 2155872257, jn2 = -2139095039

配列のキャスト

配列([N]T)をスライス([]T)や要素のポインタ([*]T)にキャストするには、&配列名を代入します。ただし配列がconstで定義されるときは代入される型にもconstが必要です。ただしスライスは配列名[始端..終端+1]配列名[0..]でも取得できます。

配列からスライスや要素のポインタへのキャスト
var varr: [3]u8 = .{ 1, 2, 3 };     // varで定義された配列
var vsarr: []u8 = ↕            // varr[0..]も可
var vparr: [*]u8 = ↕

const carr: [3]u8 = .{ 1, 2, 3 };   // constで定義された配列
var csarr: []const u8 = &carr;      // carr[0..]も可
var cparr: [*]const u8 = &carr;

また、タプルの要素の型がすべて同じであれば、同じ要素数の配列にキャストできます。また、i8からu8のように符号の有無が変わる場合は、同じ値が代入可能な場合のみ有効です。

タプルから配列へのキャスト
const tplu8 = .{ 1, 2, 3 };
const arru8: [3]u8 = tplu8;

const vtp: struct { i8, i8, i8 } = .{ 1, 2, 3 };    // .{ -1, -2, -3 }などはキャスト不可
const vau8: [3]u8 = vtp;

ポインタのキャスト

ポインタのキャストは@ptrCastを実行します。ただし、キャスト後の型によってはデータの一部を失う場合があるため、キャスト後の型と値をきちんと確認する必要があります。

キャストによって値を失う
const pu8: *u8 = @constCast(@ptrCast(&arru8));
try stdout.print("tplu8 = {any}, pu8 = {}\n", .{tplu8, pu8.*});
結果
tplu8 = { 1, 2, 3 }, pu8 = 1

キャスト後の型を明示するとき

error: @intCast must have a known result typeのようなエラーが出た時など、キャスト後の型を明示するときは@as(型名, キャスト処理)を実行します。

キャスト後の値を明示
var sum: u8 = 0;
for(1..11) |v| {
	sum += @as(u8, @intCast(v));
	// sum += v; は不可
}
// sum = 55

まとめ

  • キャスト(Cast:明示的な型変換)には代入によるものとビルトイン関数によるものとがある
  • constで変数の型が明示されずに値が代入されたとき、整数型はcomptime_int、浮動小数点型はcomptime_floatです。文字列は*const [長さ:0]u8、タプルはstruct{要素の型名, ...}となる
  • キャストされる値がconstで宣言されている場合は@constCast(キャスト処理)の実行が必要な場合がある
  • 整数型や浮動小数点型はビット幅を広げるなら代入でキャストできる
  • 整数型のビット幅を狭める(切り捨てる)場合はビルトイン関数@trucateを実行する(上位ビット切り捨て)
  • 浮動小数点型ではビット幅を問わずビルトイン関数の@floatCastを実行できるが、ビット幅を狭めたときの結果がinf(infinity:無限、あるいは範囲外)となることがある
  • 整数型と浮動小数点型とでキャストを行う場合は@floatFromIntあるいは@intFromFloatを実行する
  • 浮動小数点型から整数型にキャストするときにビット幅を超えてエラーになることがある(デバッグモードのとき)
  • 各ビットの状態を変えずにキャストするときは@bitCast関数を実行する
  • 配列([N]T)をスライス([]T)や要素のポインタ([*]T)にキャストするには、&配列名を代入するればよいが、配列がconstで定義されるときは代入される型にもconstが必要
  • スライスは配列名[始端..終端+1]配列名[0..]でも取得できる
  • タプルの要素の型がすべて同じであれば、同じ要素数の配列にキャストできる
  • ポインタのキャストは@ptrCastを実行するが、データを失う場合があるので確認が必要
  • キャスト後の型を明示するときは@as(型名, キャスト処理)を実行する

< structは構造型、型はtype、typeは値
structの組込(usingnamespace) >
ざっくりZig 一覧


脚注
  1. Casting ↩︎

  2. Explicit Casts ↩︎

Discussion