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_P
1や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ビットを見るだけなので、きわめて高速です。 ↩