これは何?
先日、untyped int の挙動で驚いたことがあったので書いておく。
Go アドベントカレンダー
の カレンダー3 の初日が空いていたので参加してみた。
コード
こんなコードがある。
const x = 9876543210
log.Printf("%v", x) // 32bit 環境ではコンパイルエラー
いつもは 64bit 環境で動かしていたんだけど、久々に 32bit 環境向けのバイナリを作ったらコンパイルエラーになってびっくりした。
x は untyped int。
log.Printf
で %v
で受けるんだから、型は何でもいい。
しかし、log.Printf
には untyped int のままでは渡せない。仕方ないので int
に変換を試みるもオーバーフローでエラー。
v ...any
に渡したいのに int
にされるのが、言われてみればまあそうだよねということだけれど、思いがけなかった。
いろいろ試してみる
const int でシフト
const x = 9876543210
const s = int(3)
log.Printf("%v", x>>s)
これはOK。エラーにならない。
untyped のまま shift できるのだろう。
const int で割る
先ほどと実質的に同じ計算だが、
const x = 9876543210
const s = int(1 << 3)
log.Printf("%v", x/s) // 32bit 環境ではコンパイルエラー
こちらはエラー。untyped のままでは int での除算はできない模様。
switch に入れる
switch 9876543210 { // 32bit 環境ではコンパイルエラー
case 1:
log.Println("one")
default:
log.Println("not one")
}
switch
に入れても勝手に int
にされる。switch int64(9876543210) {
だとエラーにならない。
case を int64
にしてみる。
switch 9876543210 { // 32bit 環境ではエラー
case int64(1): // mismatched types int64 and int
log.Println("one")
default:
log.Println("not one")
}
case
の値から switch
の方の型を推論することはできないらしい。
コンパイルエラーにならない場合
ここまでは幸いにしてコンパイルエラーになっていたが
と混ぜるとエラーにならなくなる。
const x = 100000001
s := 10
log.Println((x << s) % 1000) //=> 64bit 環境では24、32bit 環境では -80
わかりにくいし、わりと恐ろしい。
Zig ではどうなの?
「コンパイル時にしか扱えない、ビット長が確定していない値」という機能を持つ、go 以外の唯一の言語(私調べ)、Zig でどうなるか。
untyped のようなものを Printf
のようなものに渡す
Zig ではこう。
const x=987654321098765432109876543210;
try stdout.print("{}\n", .{x});
go だと「Printf
に untyped のままでは渡せないので int に変換して渡す」という処理なのでエラーになっていた。
zig だと、comptime_int
int のままで print に渡しているのでそういうトラブルは発生しない。
計算
同様にシフトと除算を試した。
const x=987654321098765432109876543210;
const ys:i32 = 10;
const zs = x>>ys;
try stdout.print("{} {}\n", .{zs, @TypeOf(zs)});
// => 964506172948013117294801311 comptime_int
const yd:i32 = 1024;
const zd = x/yd; // 除算の結果が i32 に入らないのでコンパイルエラー
try stdout.print("{} {}\n", .{zd, @TypeOf(zd)});
switch〜case のようなもの
const x = switch (987654321098765432109876543210) {
i64val, i32val, i16val, i8val, i4val => 1,
else => 0,
};
const y = switch (i64val) {
i32val, i16val, i8val, i4val => 1,
else => 0,
};
const z = switch (i64val) {
9876543210 => 1,
// 987654321098765432109876543210 => 2, // エラー: type 'i64' cannot represent integer value '987654321098765432109876543210'
else => 0,
};
zig の場合、安全であれば型が違う値でも問題なく比較できるので、あんまりエラーにならない。
ただ、go でいうところの case
にある値が switch
に渡す型で表現できない場合はエラーになる。変換できないという趣旨ではなく、どうせマッチしないからエラーにしといたよ、という話だと思う。
まとめ
untyped int が int になるタイミングはときどきわかりにくくて、32bit 環境と 64bit 環境で異なる動きをすることがあるから気をつけよう。
コンパイルエラーになる場合が多いものの、そうではないパターンもある。
zig の場合、comptime_int のままで処理が進むし、そもそも処理系依存のビット長の型を使う機会が少ないのでほとんど心配なさそう。