NEWS for Ruby 4.1.0 ↑

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

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

Language changes ↑

  • super in a module method that was called via super from a refinement method now works correctly. [Feature #22309]

  • Module#clone and Module#dup no longer rewrite the lexical scope of copied methods. Constants and class variables resolve through the original class, consistent with inheritance and mixins. [Feature #21981]

  • alias and Module#alias_method in a module emit a deprecation warning when the original method is not a method of the module or its ancestors and is only found through the fallback to Object. The fallback will be removed in Ruby 4.3, and such an alias will raise a NameError. [Bug #22276]

  • alias and Module#alias_method emit a deprecation warning when the original method is defined in a prepended module. This behavior will be removed in Ruby 4.3, and alias lookup will start at the origin class (after prepended modules). [Bug #22273]

Core classes updates ↑

Note: We’re only listing outstanding class updates.

Stdlib updates ↑

  • Psych

    • An experimental libfyaml backend has been added. It is only enabled when psych is built with --enable-libfyaml, and the default libyaml backend remains unchanged otherwise. It is not supported on Windows. [GH-psych #805]

  • Ruby::Prism

    • Ruby::Prism is added. It is the prism parser built into the interpreter, i.e., the very parser that parsed the running program, independent of the prism gem. It is autoloaded. Method#syntax_tree and others return its nodes. Its API may change between Ruby minor versions; use the prism gem unless you need the parser of the running interpreter. [Bug #22379]

The following bundled gems are added. ↑

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. ↑

  • prism 1.9.0

  • tsort 0.2.0

  • win32-registry 0.1.2

The following bundled gems are removed. ↑

  • net-ftp 0.3.9

  • net-pop 0.1.2

They are still available on rubygems.org and can be installed with gem install. [Feature #21835]

The following default gem is added. ↑

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 ↑

  • Support code for the following platforms has been removed. Their vendors or distributors have ended support, there is no environment to test Ruby on them, and none of them has a platform maintainer.

    • AmigaOS

    • AtheOS

    • BSD/OS (BSDi)

    • ESIX

    • EWS-UX (NEC EWS4800)

    • FreeBSD, NetBSD and OpenBSD with a.out binaries

    • GNU/kFreeBSD

    • GNU/kOpenSolaris

    • HI-UX/MPP

    • HP-UX on the HP 9000 Series 300

    • Interix (Windows Services for UNIX)

    • IRIX

    • MirOS BSD

    • NeXTSTEP, OpenStep and Rhapsody

    • OSF/1 (Tru64 UNIX)

    • QNX Neutrino

    • SunOS 4 (Solaris, i.e. SunOS 5, is unaffected)

    • System V Release 4

    • UNICOS/mp (Cray X1)

    • UX/PDS

  • Support code for the following old versions has been removed.

    • Cygwin 1.3 and earlier

    • Solaris 9 and earlier

    • Linux 2.6.23 and earlier (RHEL 5)

  • Windows 10 1703 or later no longer needs the LongPathsEnabled registry value to use paths longer than 260 characters. This applies to any process running the interpreter, including a program which embeds libruby. Each path component is still limited to 255 characters, and a child process still starts with the MAX_PATH limited current directory. [Bug #18947]

  • Building Ruby with MSVC now requires Visual Studio 2017 version 15.8 (_MSC_VER 1915) or later.

  • Ruby built with MSVC now requires Windows 10 version 1809 (build 17763) or Windows Server 2019 or later. Earlier Windows 10 releases and Windows Server 2016 are no longer supported.

  • Ruby on macOS now requires Mac OS X 10.6 or later. Mac OS X 10.5 and PowerPC Macs are no longer supported.

Compatibility issues ↑

Stdlib compatibility issues ↑

  • prism is now a bundled gem. Under Bundler, require "prism" needs prism in the Gemfile. Use Ruby::Prism for the parser of the running interpreter. [Bug #22379]

  • Etc.getlogin on Windows now returns the login name determined when the process starts, from the USER or USERNAME environment variable or GetUserName(). It used to follow later changes to ENV['USER'].

  • Socket

    • On Windows, a connection that is refused or unreachable now raises the matching Errno class as soon as Winsock reports it, instead of Errno::ETIMEDOUT once the whole connect_timeout has passed. Code rescuing Errno::ETIMEDOUT there has to rescue the real error instead.

    • On Windows, BasicSocket#getsockopt(:SOCKET, :ERROR) now reports an errno as it does on the other platforms, instead of the raw WinSock error code. Code comparing it with a WSAE* value has to compare it with the matching Errno::*::Errno instead.

    [Bug #18661]

C API updates ↑

Embedded TypedData ↑

  • The RUBY_TYPED_EMBEDDABLE flag is now public and documented and can be used by C extensions. It allows allocating C structs directly into Ruby object slots, which reduces pointer chasing, and in some case memory usage. See the C extension documentation for details. [Feature #21853]

  • Added new C23 inspired allocator functions, that takes the previous memory size. This allow the Ruby GC to better keep track of memory usage, improving its heuristics. It also improves the performance of system allocators that support C23 free_sized.

However, it is important to note that passing an incorrect size to these function is undefined behavior and may result in crashes or memory leaks.

  • ruby_xfree_sized(void *ptr, size_t size)

  • ruby_xrealloc_sized(void *ptr, size_t newsiz, size_t oldsiz)

  • ruby_xrealloc2_sized(void *ptr, size_t newelems, size_t newsiz, size_t oldelems)

[Feature #21861]

Ractor-scoped GC address registration ↑

  • rb_gc_register_address and rb_global_variable now register the address with the calling Ractor, and only that Ractor’s GC marks the stored object.

Any Ractor may register an address, but the value stored through it must be a special constant, a shareable object, or an unshareable object owned by the registering Ractor. Storing another Ractor’s unshareable object can result in the object being freed while the address still refers to it (use-after-free).

If the address has process lifetime (a static VALUE), register it from the main Ractor or keep the stored values shareable.

When the registering Ractor is joined with Ractor#value, remaining registrations move to the joining Ractor; otherwise they move to the main Ractor once the dead Ractor is collected.

[[Feature #22277]]

Type for lengths and indices ↑

  • rb_len_t and rb_ulen_t are added as the types of the lengths and indices of String, Array and MatchData, with LEN2NUM, NUM2LEN, ULEN2NUM, rb_len2int, PRIdLEN and its siblings, RB_LEN_MAX, RB_LEN_MIN, RB_ULEN_MAX, SIZEOF_RB_LEN_T and HAVE_RB_LEN_T. RbConfig::SIZEOF["rb_len_t"] gives its size to an extconf.rb. They are long on every platform for now, and the C API that carries such lengths, such as RSTRING_LEN and rb_range_beg_len, uses them. A later change will makes them 64 bits on mswin, so extensions should keep these lengths in rb_len_t. [Feature #22400]

  • rb_len2long is added to convert an rb_len_t to long, raising RangeError via the new rb_out_of_long when it does not fit, for extensions that keep a length in long. [Feature #22400]

Removed APIs ↑

The following APIs, which have been deprecated for many years, are removed. [Feature #21768]

  • old postponed job functions,

  • untyped data object type/functions,

  • old APIs to allocate a data object,

  • taintedness/trustedness enums/macros,

  • rb_gc_force_recycle function,

  • rb_iterate function,

  • and some functions and constants for internal use.

Implementation improvements ↑

  • The default garbage collector has been switched from a freelist allocator to a bump pointer allocator. [PR #17201]

  • The error_highlight, did_you_mean, and syntax_suggest gems are now loaded lazily on the first error display instead of at interpreter boot, which reduces startup time. Process.warmup loads them eagerly so that pre-forking servers keep their code in copy-on-write shared memory. [Feature #21951]

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.

  • The default GC now runs per Ractor: each Ractor collects its own heap on its own thread without stopping the others, and a stop-the-world collection only runs when it is really needed (explicit full GC.start, shareable-object growth, reclaiming dead Ractors’ heaps). Allocation-heavy Ractor programs now scale like forked processes.

  • GC.start(global: false) can be used when multiple Ractors are running to force a Ractor-local GC. By default, GC.start runs a global GC (all Ractors) like before. This can also be triggered with GC.start(global: true). The global keyword argument has no effect when a single Ractor is running.

Visible behavior changes:

One port can therefore watch a whole group, which is all a supervisor needs:

  workers.each { |r| r.monitor port }

  until workers.empty?
    r, status = port.receive
    workers.delete(r)
    workers << restart(r) if status == :aborted
  end

M:N thread scheduler ↑

  • The scheduler scales with the number of waiters and of Ractors, where it used to walk a list or take one lock for all of them:

  • A timed wait sits in a hierarchical timer wheel rather than on a list sorted by deadline, which was inserted into by a linear scan.

  • An fd stays armed in the backend between waits, instead of being added before each wait and removed after each wake.

  • The io-wait bookkeeping is sharded by fd, rather than serialized on one lock across every fd.

  • A timed wait on an fd rides the scheduler instead of going to a blocking region, which cost a native thread handoff per wait.

  • A context switch, and leaving or rejoining the shared pool, no longer take the scheduler's global lock.

  • The RUBY_MN_THREADS environment variable now runs from no M:N scheduling at all to all of it. -1 is new: a Ractor’s threads have been M:N since the scheduler was added, with no way to turn that off. 0 and 1 are unchanged.

| | main thread | the main Ractor’s other threads | a Ractor’s threads | |—|—|—|—| | -1 | 1:1 | 1:1 | 1:1 | | 0 or unset | 1:1 | 1:1 | M:N | | 1 | 1:1 | M:N | M:N | | 2 | M:N | M:N | M:N |

  • RUBY_MN_THREADS=2 is new. The main thread is resumed like any other M:N thread rather than woken on a native thread of its own, which costs an order of magnitude more. It pays off when the main thread drives the work, and does nothing for one that only starts other threads and waits.

The main thread is then no longer bound to one OS thread, which is what the M:N scheduler already meant for every other thread:

  • A C extension that keeps state per OS thread has to call rb_thread_lock_native_thread().

  • What must run on the process's initial thread does not work at all, pinning included: macOS AppKit and CFRunLoop, and hosts that embed Ruby and return into their own main loop.

  • The OS thread name is no longer set from the Ruby thread for M:N threads: one native thread runs many of them over its life. Thread#name= was already skipped for the same reason.

JIT ↑

YJIT ↑

  • YJIT now supports Windows (mswin) on x64 CPUs. Configure with win32\configure.bat --enable-yjit, which requires rustc with the x86_64-pc-windows-msvc target. Only the release build is supported, and MinGW is not. [Feature #18439]