November 9, 2016·4 min read·engineeringrubyintegerscompatibility

Ruby 2.4 preview separates Integer from its storage representation

Ruby 2.4 preview 3 makes Integer the visible class for small and large integers. Native extensions still need to distinguish their internal representations, and the preview exposes that compatibility boundary before release.

An integer's magnitude should not usually decide which public class application code sees. Ruby has exposed Fixnum and Bignum as separate classes even though ordinary arithmetic moves between their representations. Ruby 2.4's third preview unifies the visible classes as Integer while keeping a representation distinction available to native code.

The November 9 preview announcement asks the community for feedback while features can still change. It specifically warns that C extensions touching the Fixnum or Bignum class need repairs. That warning is important because the Ruby-level simplification does not mean every native operation can now treat all integer values identically.

I came to Ruby from Perl and find its object model more consistent for larger object-oriented programs. Integer unification fits that preference. Application code can ask whether a value is an integer without exposing an implementation choice about how a particular magnitude is stored.

Public class identity and native representation diverge

The preview's NEWS file describes Fixnum and Bignum as aliases of Integer at the Ruby level. Existing kind_of? checks using those constants therefore behave as Integer checks. That preserves some source compatibility, but it changes what a check means if the code previously intended to distinguish small integers from large ones.

The visible result can be demonstrated without assuming a machine-specific Fixnum boundary. Both a small integer and a deliberately much larger one report Integer as their class under the preview.

1.class == Integer
(2 ** 100).class == Integer

The NEWS file recommends 0.class == Integer as a Ruby-level feature check. At the C level it provides RUBY_INTEGER_UNIFICATION to detect the change. Native code should distinguish representations with FIXNUM_P(obj) and RB_TYPE_P(obj, T_BIGNUM). The removed class constants rb_cFixnum and rb_cBignum can cause extension compilation to fail.

These instructions make the intended boundary explicit. A Ruby class test describes the public numeric category. A native representation predicate describes which internal layout a C implementation can operate on. Replacing every representation check with an Integer class check would discard information that native code still needs.

Compatibility aliases also deserve attention in pure Ruby code. A check against Fixnum can continue to execute while accepting values that previously belonged to Bignum. The absence of a missing-constant error does not establish that the old branch condition retains its meaning. Code concerned with a numeric bound should state that bound directly instead of using the old class split as an indirect test.

The tagged implementation in numeric.c registers Integer as a subclass of Numeric and binds the deprecated Fixnum constant to that class. This makes the alias behavior an inspectable implementation decision, rather than merely an interpretation of the release headline.

The preview makes package compatibility observable

Preview 3 is a request for feedback, not the final stable release. Its value to gem maintainers is the opportunity to try the new class behavior before the release is settled. The native-extension warning identifies a specific class of build failure, while the aliases identify a class of behavior changes that may leave source syntactically valid.

This is a useful distinction when interpreting a package test result. An extension failing to compile because it references a removed C constant has a different problem from a Ruby test whose old Fixnum condition now accepts every Integer. Both originate in the same unification, but the repair needs to preserve the intent of the affected operation.

The preview also changes Unicode case mapping. String and Symbol case methods now handle Unicode instead of only ASCII. The NEWS file names supported encodings and points out that callers requiring ASCII-only behavior must request :ascii. ASCII input alone does not require a change merely because the implementation now supports a wider character set.

That is another case where an API name remains familiar while the meaning expands. A program may have tolerated the ASCII limitation, or it may have relied on it. The new behavior helps the former directly. The latter needs an explicit restriction, particularly where a protocol's text rules differ from natural-language case conversion.

Predicate matching avoids unrelated state changes

The new Regexp#match? is a smaller example of making intent more specific. It answers whether a match exists without creating the back-reference object or changing $~. A caller that needs capture data should continue to use an operation that provides it. A caller that needs only a Boolean can avoid that additional state.

The difference matters when code performs several regular-expression operations. A predicate check need not overwrite match information from another operation. The preview announcement also explains the allocation motivation, but it supplies no universal speedup for an application. The concrete change is the absence of back-reference creation and mutation for this method.

Other preview work includes an internal hash-table redesign using open addressing and an inclusion-order array. That affects the implementation beneath familiar Hash operations. The announcement credits Vladimir Makarov and discussion with Yura Sokolov. It is another reason to exercise real package workloads during the preview, although it should not be conflated with the public Integer class change.

Debugging gains explicit entry points

binding.irb starts an interactive IRB session from a binding, providing a debugging operation similar to the existing binding.pry workflow. The preview also introduces exception-reporting controls for threads and expands deadlock reports with backtraces and thread dependencies. The announcement still asks for feedback on the exception-reporting default, so that default should not be treated as a settled release guarantee.

I find Ruby's readability useful, but a concise program still needs visible failure behavior. Reporting an unhandled thread exception and explaining a deadlock address information that can otherwise be absent from a debugging session. Those changes complement the language simplifications without depending on them.

For this preview, Integer unification remains the compatibility change with the clearest split between Ruby and native code. Ruby sees one numeric class, while extensions retain explicit representation predicates. Gem maintainers can use that distinction to preserve the purpose of their checks while the preview remains open to feedback, instead of treating every surviving Fixnum reference as evidence that the old behavior is unchanged.