Ruby 2.4から、整数のクラスが付け変わることになりますが、特にCエクステンションを書いている場合は注意の必要があります。
Ruby 2.3までの世界
Rubyの世界にももちろん整数がありますが、(Ruby側から見て)今までは抽象クラスのIntegerの下に、絶対値の小さな整数についてはFixnum、それに収まらないものについてはBignumという具象クラスがある、というように分かれていました。このあたりについては、以前書いた記事が参考になるかと思います。
問題点
このような形にしていることで、いくつか問題がありました。
-
FixnumとBignumは実装の詳細の違いで、Ruby側から違いが見える必然性はない - 同じ整数なのに
FixnumとBignumで挙動を違える、なんてことはむしろやるべきでない - 環境によって
Fixnumのサイズが違う(可搬性に乏しい) - RubyのISO規格上も、
Integer以下は実装依存となっている
Ruby 2.4での変更点
そんなこともあって、Ruby 2.4からは両者がIntegerへ統合されることになりました。
Ruby側から見て
ふつうにプログラムを書いている中では、そもそもFixnumやBignumを直接使う場面自体が少なかったでしょうし、そこまで大問題とはならないと思いますが、いくつか注意すべき点はあります。
既存のプログラムとの互換上、FixnumとBignumという定数は存在し続けますが、どちらも**Integerのエイリアス**となります。それぞれをオープンクラスなどしていた場合も、直接Integerを変更するような形となります(きちんと対応する形に修正した方がいいでしょう)。
そして、foo.is_a?(Fixnum)のようにFixnumに収まるかチェックしていた場合、Ruby 2.4ではfoo.is_a?(Integer)とみなされて、整数であれば常にtrueとなります。(Cエクステンションが絡むような状況なら別として)Fixnumかどうかをチェックするという意図自体が根本的に問題なので、きちんと範囲チェックするように修正しましょう。
あと、これは実際に影響する場面もあるかもしれませんが、FixnumやBignumという定数そのものをDSLの一部に使っていた場合、Integerと同じ値になってしまうのでうまく動かなくなります。使っているgemの説明に従って、適切に置き換えましょう。
C言語側から見て
Ruby上ではFixnumとBignumの区別は廃止されましたが、C言語の世界では2種類の内部構造がそのまま存続する形となっています。ということで、「整数を扱う部分をすべて書き換える必要がある」なんてことにはなりません。
ただし、C言語上ではFixnumとBignumを表すrb_cFixnumとrb_cBignumが廃止されています。これらの定数を使っていた場合、コンパイルエラーとなります。代替策としては、
- 型チェックに使っていた場合→
FIXNUM_P1やRB_TYPE_Pなどで置き換えましょう。内部データ構造を示すためのT_BIGNUMは健在です。 - メソッド定義などをしていた場合→
Integer(rb_cInteger)にセットする形に切り替えましょう。Fixnum用とBignum用の関数が別にある場合、内部で振り分けする必要が出てきます。
また、これらの振り分けを支援するための定数として、RUBY_INTEGER_UNIFICATIONが定義されています。#ifdef RUBY_INTEGER_UNIFICATIONで切り分けましょう。
自作gemのチェック
以前に作っていたjkr2255/bit_utils(解説)について、コードをチェックしてみましたが、内部的にはFixnum用とBignum用のメソッドがあって、
-
Fixnum用…Bignumを投げるとエラーになる -
Bignum用…Fixnumでも動くけど、少し余分な処理が入る
というようになっていました。そして、Fixnumをオープンして使うとき以外はBignum用をそのまま使うようになっていて、オープンクラス時もFixnum→Bignumの順で行っていたので、そのままですべてがうまくいくような流れになっていました。Ruby 2.4.0-preview2でも正常に動作しました。
とはいえ、FixnumとBignumを両方オープンしているのも行儀が悪いので、書き換えを行う予定です。
参考資料
-
1ビットを見るだけなので、きわめて高速です。 ↩