NEWS for Ruby 4.0.0

This document is a list of user-visible feature changes since the 3.4.0 release, except for bug fixes.

Note that each entry is kept to a minimum, see links for details.

Language changes

  • *nil no longer calls nil.to_a, similar to how **nil does not call nil.to_hash. [Feature #21047]

  • Logical binary operators (||, &&, and and or) at the beginning of a line continue the previous line, like fluent dot. The following code examples are equal:

    if condition1
       && condition2
      ...
    end
    

    Previously:

    if condition1 && condition2
      ...
    end
    
    if condition1 &&
       condition2
      ...
    end
    

    [Feature #20925]

Core classes updates

Note: We’re only listing outstanding class updates.

Stdlib updates

We only list stdlib changes that are notable feature changes.

Other changes are listed in the following sections. We also listed release history from the previous bundled version that is Ruby 3.4.0 if it has GitHub releases.

The following bundled gems are promoted from default gems.

The following default gem is added.

  • win32-registry 0.1.2

The following default gems are updated.

The following bundled gems are updated.

RubyGems and Bundler

Ruby 4.0 bundled RubyGems and Bundler version 4. see the following links for details.

Supported platforms

  • Windows

    • Dropped support for MSVC versions older than 14.0 (_MSC_VER 1900). This means Visual Studio 2015 or later is now required.

Compatibility issues

  • The following methods were removed from Ractor due to the addition of Ractor::Port:

    • Ractor.yield

    • Ractor#take

    • Ractor#close_incoming

    • Ractor#close_outgoing

    [Feature #21262]

  • ObjectSpace._id2ref is deprecated. [Feature #15408]

  • Process::Status#& and Process::Status#>> have been removed. They were deprecated in Ruby 3.3. [Bug #19868]

  • rb_path_check has been removed. This function was used for $SAFE path checking which was removed in Ruby 2.7, and was already deprecated. [Feature #20971]

  • A backtrace for ArgumentError of "wrong number of arguments" now include the receiver's class or module name (e.g., in Foo#bar instead of in bar). [Bug #21698]

  • Backtraces no longer display internal frames. These methods now appear as if it is in the Ruby source file, consistent with other C-implemented methods. [Bug #20968]

Before: ruby -e '[1].fetch_values(42)' <internal:array>:211:in 'Array#fetch': index 42 outside of array bounds: -1...1 (IndexError) from <internal:array>:211:in 'block in Array#fetch_values' from <internal:array>:211:in 'Array#map!' from <internal:array>:211:in 'Array#fetch_values' from -e:1:in '<main>'

After: $ ruby -e '[1].fetch_values(42)' -e:1:in 'Array#fetch_values': index 42 outside of array bounds: -1...1 (IndexError) from -e:1:in '<main>'

Stdlib compatibility issues

C API updates

  • IO

    • rb_thread_fd_close is deprecated and now a no-op. If you need to expose file descriptors from C extensions to Ruby code, create an IO instance using RUBY_IO_MODE_EXTERNAL and use rb_io_close(io) to close it (this also interrupts and waits for all pending operations on the IO instance). Directly closing file descriptors does not interrupt pending operations, and may lead to undefined behaviour. In other words, if two IO objects share the same file descriptor, closing one does not affect the other. [Feature #18455]

  • GVL

    • rb_thread_call_with_gvl now works with or without the GVL. This allows gems to avoid checking ruby_thread_has_gvl_p. Please still be diligent about the GVL. [Feature #20750]

  • Set

    • A C API for Set has been added. The following methods are supported: [Feature #21459]

      • rb_set_foreach

      • rb_set_new

      • rb_set_new_capa

      • rb_set_lookup

      • rb_set_add

      • rb_set_clear

      • rb_set_delete

      • rb_set_size

Implementation improvements

  • Class#new (ex. Object.new) is faster in all cases, but especially when passing keyword arguments. This has also been integrated into YJIT and ZJIT. [Feature #21254]

  • GC heaps of different size pools now grow independently, reducing memory usage when only some pools contain long-lived objects

  • GC sweeping is faster on pages of large objects

  • “Generic ivar” objects (String, Array, TypedData, etc.) now use a new internal “fields” object for faster instance variable access

  • The GC avoids maintaining an internal id2ref table until it is first used, making object_id allocation and GC sweeping faster

  • object_id and hash are faster on Class and Module objects

  • Larger bignum Integers can remain embedded using variable width allocation

  • Random, Enumerator::Product, Enumerator::Chain, Addrinfo, StringScanner, and some internal objects are now write-barrier protected, which reduces GC overhead.

Ractor

A lot of work has gone into making Ractors more stable, performant, and usable. These improvements bring Ractor implementation closer to leaving experimental status.

  • Performance improvements

    • Frozen strings and the symbol table internally use a lock-free hash set [Feature #21268]

    • Method cache lookups avoid locking in most cases

    • Class (and generic ivar) instance variable access is faster and avoids locking

    • CPU cache contention is avoided in object allocation by using a per-ractor counter

    • CPU cache contention is avoided in xmalloc/xfree by using a thread-local counter

    • object_id avoids locking in most cases

  • Bug fixes and stability

    • Fixed possible deadlocks when combining Ractors and Threads

    • Fixed issues with require and autoload in a Ractor

    • Fixed encoding/transcoding issues across Ractors

    • Fixed race conditions in GC operations and method invalidation

    • Fixed issues with processes forking after starting a Ractor

    • GC allocation counts are now accurate under Ractors

    • Fixed TracePoints not working after GC [Bug #19112]

JIT

  • ZJIT

    • Introduce an experimental method-based JIT compiler. Where available, ZJIT can be enabled at runtime with the --zjit option or by calling RubyVM::ZJIT.enable. When building Ruby, Rust 1.85.0 or later is required to include ZJIT support.

    • As of Ruby 4.0.0, ZJIT is faster than the interpreter, but not yet as fast as YJIT. We encourage experimentation with ZJIT, but advise against deploying it in production for now.

    • Our goal is to make ZJIT faster than YJIT and production-ready in Ruby 4.1.

  • YJIT

    • RubyVM::YJIT.runtime_stats

      • ratio_in_yjit no longer works in the default build. Use --enable-yjit=stats on configure to enable it on --yjit-stats.

      • Add invalidate_everything to default stats, which is incremented when every code is invalidated by TracePoint.

    • Add mem_size: and call_threshold: options to RubyVM::YJIT.enable.

  • RJIT

    • --rjit is removed. We will move the implementation of the third-party JIT API to the ruby/rjit repository.