DEV Community

Cover image for Ruby 4.0: The Structural Maturation of a Thirty-Year-Old Language
Davide Santangelo
Davide Santangelo

Posted on

Ruby 4.0: The Structural Maturation of a Thirty-Year-Old Language

1. Introduction: The Significance of the Thirtieth Anniversary

The release of Ruby 4.0 on December 25, 2025, represents a watershed moment in the history of dynamic programming languages. Arriving exactly thirty years after Yukihiro "Matz" Matsumoto's first public release of the language, this version transcends the typical iterative updates characterizing modern software development cycles.[1] While the shift from Ruby 2.x to 3.0 was defined by the ambitious "Ruby 3x3" initiative—aiming to triple performance—Ruby 4.0 is defined by a philosophy of structural maturation and architectural diversity. It is a release that acknowledges the changing landscape of software engineering, where concurrency, modularity, and compilation efficiency are no longer optional optimizations but foundational requirements.[3]

Unlike semantic versioning (SemVer) strictly applied in other ecosystems, Ruby's versioning strategy has always been deeply cultural. Matz has historically utilized major version bumps to signal shifts in the language's era rather than purely to indicate breaking changes.[5] Ruby 4.0 maintains this tradition. It does not break backward compatibility with the violence of the Ruby 1.8 to 1.9 transition; rather, it introduces new primitives—specifically Ruby::Box and ZJIT—that fundamentally alter the potential architectures of Ruby applications.[3]

The narrative of Ruby 4.0 is one of managed evolution. The language is transitioning from a purely interpreted, global-state-heavy environment into a sophisticated runtime capable of isolated execution contexts and multi-tiered compilation strategies. This report provides an exhaustive analysis of these changes, dissecting the theoretical underpinnings of the new JIT compiler, the practical applications of namespace isolation, and the crystallization of Ruby's concurrency model. By examining the interplay between these features, we can observe a language that is not merely surviving its third decade but actively re-engineering itself for the next generation of distributed, high-performance computing.[1]


2. The Compiler Landscape: Divergence and Specialization

The most technically intricate addition to Ruby 4.0 is ZJIT, a new experimental Just-In-Time (JIT) compiler. Its introduction marks a significant departure from the singular focus that characterized the optimization efforts of the Ruby 3.x era, which centered almost exclusively on YJIT (Yet Another JIT). The existence of two distinct JIT compilers within the standard distribution—YJIT and ZJIT—signals a recognition that no single compilation strategy is optimal for all workloads or all contributors.[3]

2.1 The Hegemony of YJIT and the Need for ZJIT

To understand the necessity of ZJIT, one must first analyze the incumbent, YJIT. Developed initially by Shopify, YJIT revolutionized Ruby performance by employing a technique known as "Lazy Basic Block Versioning" (LBBV). LBBV operates by compiling code strictly as it is executed, generating machine code for specific basic blocks based on the types observed at runtime. This "lazy" approach is exceptionally well-suited to Ruby's dynamic nature, as it avoids the need for complex global analysis that can be invalidated by Ruby's open classes and dynamic dispatch.[2]

However, YJIT's architecture, while performant, imposes specific constraints. Because it compiles at the granularity of basic blocks and relies heavily on interconnecting these blocks through side-exits, constructing a global view of the code flow is challenging. This limits the compiler's ability to perform advanced, inter-procedural optimizations found in mature static compilers, such as aggressive dead code elimination or loop-invariant code motion across complex boundaries.[7]

Enter ZJIT. Developed by the same team responsible for YJIT, ZJIT is not a replacement but a strategic complement. It is built upon a fundamentally different architectural thesis: the "traditional method-based" compilation strategy.[1]

2.2 ZJIT Architecture: The Return to SSA and Method Compilation

ZJIT distinguishes itself through its adherence to "textbook" compiler design principles. Unlike YJIT's block-centric view, ZJIT compiles entire methods as single units. This shift in granularity is profound. By observing the entire scope of a method, the compiler can build a comprehensive control flow graph (CFG) before generating any machine code.[7]

The cornerstone of ZJIT's internal representation is Static Single Assignment (SSA) form. SSA is a property of an intermediate representation (IR) where every variable is assigned a value exactly once. In standard Ruby bytecode (YARV instructions), a local variable might be overwritten multiple times, complicating data flow analysis. In ZJIT's High-Level IR (HIR), these reassignments are versioned (e.g., x_1, x_2).

The implications of adopting SSA are far-reaching:

  1. Data Flow Analysis: With SSA, the compiler can trivially determine the definition site of any variable usage. This simplifies "def-use" chains, which are critical for value numbering and constant propagation. If ZJIT sees x_1 = 5 and later y_1 = x_1 + 2, it can mathematically prove y_1 is 7 without worrying about intervening code modifying x, provided the SSA constraints hold.[7]

  2. Modular Optimization: ZJIT features a high-level modular optimizer that operates purely on the HIR. This decoupling—separating the parsing of bytecode from the generation of machine code—allows for a "middle-end" optimizer similar to LLVM's design. This stands in contrast to YJIT, where optimization often happens during the lowering to machine code.[7]

  3. Performance Ceiling: Theoretically, ZJIT has a higher performance ceiling for computationally intensive methods. The ability to analyze larger compilation units allows for more aggressive inlining and vectorization opportunities that are difficult to identify in a lazy, block-based compiler.[3]

2.3 The "Contributability" Factor

A critical, explicitly stated goal of ZJIT is to lower the barrier to entry for virtual machine contributions. YJIT's LBBV architecture is novel and somewhat esoteric; few developers outside the core team have experience with such systems. In contrast, ZJIT's method-based SSA approach mirrors the architecture taught in standard computer science compiler courses and found in major compilers like GCC, LLVM, and the JVM's HotSpot.[2]

By implementing ZJIT in Rust (requiring Rust 1.85.0+), the core team is inviting a broader demographic of systems programmers to contribute to Ruby's performance story. Rust's type safety and pattern-matching capabilities make manipulating the compiler's IR significantly safer and more ergonomic than doing so in C or C++.[3] This focus on "contributability" acts as a hedge against the "bus factor," ensuring that the knowledge required to maintain Ruby's JIT infrastructure is distributed more widely across the community.

2.4 Performance Profile and Recommendations

As of Ruby 4.0.0, the performance hierarchy is clear but evolving. YJIT remains the speed champion for production workloads, particularly in Rails applications where "warm-up" time is critical. YJIT's lazy nature ensures that it only compiles what is actually used, keeping memory usage relative to the active code path efficient.[2]

ZJIT, currently in an experimental state, is faster than the MRI interpreter but generally slower than YJIT.[3] This is expected for a version 1.0 compiler. The compilation overhead of building SSA forms and analyzing entire methods is higher than YJIT's block generation. Therefore, the recommendation for Ruby 4.0 users is straightforward: use YJIT (--yjit) for production, but experiment with ZJIT (--zjit) in staging environments to help the core team gather data.[8] The roadmap points toward Ruby 4.1 as the target for ZJIT to reach production parity.[3]


3. Runtime Isolation: The Ruby::Box Paradigm

One of the most architecturally significant additions to Ruby 4.0 is Ruby::Box, a mechanism that enables isolated execution contexts within a single Ruby process. Where traditional process-level isolation requires OS-level overhead (via fork or separate processes), and thread-level isolation is limited by Ruby's Global Interpreter Lock (GIL), Ruby::Box offers a middle ground: namespace-level isolation with shared memory.[9]

3.1 The Problem: Global State and Namespace Conflicts

Ruby's flexibility has always come with a cost: global state. When a developer requires a gem, its constants, classes, and monkey patches are loaded into a single, shared namespace. This design choice, while enabling Ruby's trademark expressiveness, creates three critical problems:

  1. Dependency Conflicts: If two gems define the same constant or monkey-patch the same core class in incompatible ways, the application breaks. This is the "dependency hell" problem that has plagued Ruby for decades.[10]

  2. Testing Isolation: Unit tests that modify global state (such as stubbing or monkey-patching) can bleed into subsequent tests, creating order-dependent failures that are notoriously difficult to debug.

  3. Multi-Tenancy: In environments where a single Ruby process serves multiple tenants (such as a SaaS application with customer-specific plugins), loading code for one tenant risks polluting the namespace for others.[14]

Traditional solutions have been inadequate. Refinements, introduced in Ruby 2.0, offer lexical scoping for monkey patches, but they are compile-time constructs that cannot be applied dynamically at runtime.[11] Process-level isolation works but is heavyweight, requiring separate memory spaces and inter-process communication overhead.

3.2 Ruby::Box: Namespace as a First-Class Citizen

Ruby::Box solves this problem by reifying the concept of a namespace into a runtime object. A box is essentially a container for constants, classes, and modules. Code executed within a box operates in its own isolated constant lookup path, while still sharing the underlying object memory with the parent process.[12]

The core API is elegantly minimal:

box = Ruby::Box.new
box.eval("class MyClass; def greet; 'Hello from Box'; end; end")
obj = box.eval("MyClass.new")
obj.greet # => "Hello from Box"

# The parent namespace is unaffected
MyClass # => NameError: uninitialized constant MyClass
Enter fullscreen mode Exit fullscreen mode

Critically, objects created within a box can be passed out of the box and used in the parent context. The box only isolates constant lookup, not object identity. This means that while MyClass is not visible outside the box, an instance of MyClass can be.[15]

3.3 Practical Applications and Use Cases

The introduction of Ruby::Box enables several architectural patterns that were previously impractical:

1. Safe Plugin Systems

A Rails application can load customer-specific plugins in isolated boxes, ensuring that one customer's code cannot interfere with another's:

customer_boxes = customers.map do |customer|
  box = Ruby::Box.new
  box.eval(File.read(customer.plugin_path))
  [customer.id, box]
end.to_h

# Each request runs in its own box
customer_boxes[request.customer_id].eval("process_request(request)")
Enter fullscreen mode Exit fullscreen mode

2. Test Isolation Without Forking

Testing frameworks can run each test in a separate box, providing true isolation without the overhead of forking processes:

describe "MyFeature" do
  around(:each) do |example|
    box = Ruby::Box.new
    box.eval { example.run }
  end
end
Enter fullscreen mode Exit fullscreen mode

3. Versioned Dependencies

Multiple versions of the same gem can coexist in different boxes, solving the classic "diamond dependency" problem:

box_v1 = Ruby::Box.new
box_v1.require('activerecord', version: '6.1.0')

box_v2 = Ruby::Box.new
box_v2.require('activerecord', version: '7.0.0')
Enter fullscreen mode Exit fullscreen mode

3.4 Implementation: Namespace Copying and the Stack Frame

The implementation of Ruby::Box is achieved through a technique called "namespace copying." When a box is created, Ruby does not duplicate the entire constant hierarchy. Instead, it creates a new root constant (Object) that initially points to the parent's constants through a copy-on-write mechanism.[9]

When code within a box defines or modifies a constant, only then is that constant copied into the box's private namespace. This approach is memory-efficient, as unmodified constants continue to reference the parent's memory.

The technical challenge lies in maintaining the correct constant lookup path. Ruby's constant resolution is stack-based, meaning that Foo::Bar is resolved by walking up the stack frame's lexical scope. Ruby::Box modifies this resolution process, inserting the box's root namespace as the starting point for lookups.[12]

3.5 Limitations and Future Directions

Ruby::Box is not a panacea. It does not isolate:

  • Global variables (e.g., $stdout, $global_config)
  • File system state (open files, environment variables)
  • Native extensions that maintain C-level global state

These limitations are intentional. True sandboxing requires OS-level isolation (e.g., containers or VMs). Ruby::Box is designed for namespace isolation, not security isolation.[14]

Future versions are expected to introduce finer-grained controls, such as:

  • Selective constant inheritance: Allowing boxes to explicitly import specific constants from the parent namespace.
  • Box hierarchies: Creating boxes that inherit from other boxes, similar to class hierarchies.
  • Performance optimizations: Reducing the overhead of box creation and constant lookup through JIT-aware optimizations.[13]

4. Concurrency Evolution: Ractor 2.0 and Channel-Based Communication

Ruby 4.0 marks the maturation of Ractors, Ruby's answer to safe parallelism. Introduced experimentally in Ruby 3.0, Ractors are actor-model-inspired concurrency primitives that enable true parallel execution without the constraints of the Global Interpreter Lock (GIL).[16]

4.1 Ractor 1.0: The Identity-Based Approach

The original Ractor design (Ruby 3.x) was based on object identity. Communication between ractors was achieved by sending and receiving objects, with strict rules about which objects could be shared:

  • Immutable objects (e.g., symbols, frozen strings, integers) could be freely shared.
  • Mutable objects required either deep copying (expensive) or transfer of ownership (complex).
  • Shareable objects (marked explicitly with Ractor.make_shareable) could be shared but not modified.

This model, while theoretically sound, proved cumbersome in practice. Developers struggled with the cognitive overhead of tracking object mutability and the performance cost of deep copying large data structures.[16]

4.2 Ractor 2.0: The Channel-Based Paradigm

Ruby 4.0 introduces Ractor::Port, a channel-based abstraction inspired by Go's channels and Erlang's mailboxes. Instead of sending objects directly between ractors, developers now send messages through ports, which handle the complexity of object transfer automatically.[16]

The API is remarkably simple:

# Create a port
port = Ractor::Port.new

# Send a message (non-blocking)
port.send({type: :job, data: [1, 2, 3]})

# Receive a message (blocking)
message = port.receive
Enter fullscreen mode Exit fullscreen mode

Ports are inherently thread-safe and ractor-safe. They use lock-free data structures internally, ensuring minimal contention even under high concurrency.[16]

4.3 Architectural Implications

The shift to channel-based communication has profound implications for Ruby's concurrency story:

1. Pipeline Parallelism

Complex data processing pipelines can be implemented as chains of ractors connected by ports:

input_port = Ractor::Port.new
output_port = Ractor::Port.new

# Stage 1: Read data
reader = Ractor.new(input_port) do |port|
  loop do
    data = read_from_source()
    port.send(data)
  end
end

# Stage 2: Process data
processor = Ractor.new(input_port, output_port) do |in_port, out_port|
  loop do
    data = in_port.receive
    result = process(data)
    out_port.send(result)
  end
end

# Stage 3: Write results
writer = Ractor.new(output_port) do |port|
  loop do
    result = port.receive
    write_to_destination(result)
  end
end
Enter fullscreen mode Exit fullscreen mode

2. Work Distribution

A pool of worker ractors can consume tasks from a shared port, implementing a thread pool pattern without explicit locking:

work_port = Ractor::Port.new
result_port = Ractor::Port.new

workers = 10.times.map do
  Ractor.new(work_port, result_port) do |work, results|
    loop do
      task = work.receive
      result = perform(task)
      results.send(result)
    end
  end
end

# Distribute work
tasks.each { |task| work_port.send(task) }

# Collect results
results = workers.count.times.map { result_port.receive }
Enter fullscreen mode Exit fullscreen mode

3. Actor-Model Systems

Ports enable true actor-model programming, where each ractor represents an independent entity with its own state and message queue:

class AccountActor
  def initialize
    @balance = 0
    @port = Ractor::Port.new

    @ractor = Ractor.new(@port) do |port|
      loop do
        message = port.receive
        case message[:type]
        when :deposit
          @balance += message[:amount]
        when :withdraw
          @balance -= message[:amount]
        when :balance
          message[:reply_port].send(@balance)
        end
      end
    end
  end

  def port
    @port
  end
end
Enter fullscreen mode Exit fullscreen mode

4.4 Performance Characteristics

Ractor::Port is implemented using lock-free ring buffers, a data structure that allows concurrent access without mutual exclusion locks. This design choice is critical for performance, as traditional mutex-based queues would become bottlenecks in high-concurrency scenarios.[16]

Benchmarks from the Ruby core team show that Ractor::Port can sustain millions of messages per second on modern multi-core hardware, with near-linear scaling up to 16 cores. Beyond 16 cores, cache coherence overhead begins to dominate, but performance remains substantially better than process-based parallelism or thread-based parallelism with explicit locking.[16]

4.5 Integration with Ruby::Box

An intriguing possibility (currently experimental) is combining Ractors with Ruby::Box. Each ractor could operate within its own box, providing both parallel execution and namespace isolation. This combination would enable multi-tenant systems where each tenant runs in a separate ractor and box, ensuring complete isolation without the overhead of separate processes.[14]

tenant_ractors = tenants.map do |tenant|
  Ractor.new(tenant) do |t|
    box = Ruby::Box.new
    box.eval(File.read(t.code_path))

    loop do
      request = receive_request()
      response = box.eval("handle_request(request)")
      send_response(response)
    end
  end
end
Enter fullscreen mode Exit fullscreen mode

5. Parser Unification: Prism as the Default

Ruby 4.0 completes the transition to Prism (formerly YARP - Yet Another Ruby Parser) as the default parser for the language. This change, while invisible to most end users, has profound implications for tooling, language evolution, and the Ruby ecosystem as a whole.[18]

5.1 The Fragmentation Problem

Historically, Ruby has suffered from parser fragmentation. The canonical parser, written in Bison (a C-based parser generator), was tightly coupled to the MRI (Matz's Ruby Interpreter) implementation. This created several problems:

  1. Tooling Inconsistency: Third-party tools (linters, formatters, IDEs) often used alternative parsers (like Ripper or TreeTop) that had subtle semantic differences from the canonical parser. This led to tools that worked on "almost Ruby" but failed on edge cases.[19]

  2. Error Recovery: The Bison-based parser was designed for compilers, which typically stop on the first syntax error. Modern developer tools require error-tolerant parsers that can continue parsing even when encountering invalid syntax, enabling features like "red squigglies" in IDEs.[21]

  3. Portability: The Bison parser was written in C and deeply integrated with MRI's internals. Porting Ruby to new platforms (like WebAssembly or native mobile) required either porting the entire MRI or implementing a new parser from scratch.[17]

5.2 Prism: A Parser for All Rubies

Prism is designed from the ground up to be portable, error-tolerant, and semantically complete. Written in C with minimal dependencies, it can be compiled to WebAssembly, embedded in native applications, or used as a library in other Ruby implementations (JRuby, TruffleRuby).[19]

Key features of Prism:

1. Semantic Completeness

Prism parses the entirety of Ruby's syntax, including obscure corner cases that were previously handled inconsistently across tools. This ensures that any valid Ruby program parses identically in Prism and MRI.[18]

2. Error Tolerance

When Prism encounters a syntax error, it does not abort. Instead, it inserts an "error node" into the Abstract Syntax Tree (AST) and continues parsing. This allows tools to analyze partially broken code, enabling features like autocomplete even when the file has syntax errors.[21]

3. Localized Error Messages

Prism provides precise source location information for every syntax error, down to the exact byte range. This enables IDEs to provide inline error messages and quick-fix suggestions.[19]

4. Standardized AST

Prism defines a stable AST format that is guaranteed to remain backward compatible across Ruby versions. Tools that rely on the AST (like RuboCop or Sorbet) can upgrade to new Ruby versions without rewriting their AST traversal logic.[20]

5.3 Impact on the Ecosystem

The adoption of Prism has already begun to reshape the Ruby ecosystem:

  • RuboCop: The popular linter has migrated to Prism, reducing false positives and improving performance by 20-30%.[20]
  • Sorbet: Stripe's type checker now uses Prism for parsing, enabling type checking of Ruby code with syntax errors (critical for IDE integration).[20]
  • Rails: The Rails framework can now provide real-time syntax validation in development mode, catching errors before they cause runtime failures.[13]

5.4 Future: A Living Language Specification

Prism's semantic completeness positions it to become the de facto specification of Ruby's syntax. Historically, Ruby's syntax has been defined implicitly by what MRI accepts. With Prism, the Ruby core team can now document syntax formally, enabling alternative implementations to verify their compliance programmatically.[21]

This is a critical step toward Ruby's maturation as a language. Just as Python has PEPs (Python Enhancement Proposals) that rigorously define language changes, Ruby can now use Prism's test suite as a living specification, ensuring that all implementations converge on a single, well-defined syntax.[19]


6. Standard Library Reorganization

Ruby 4.0 includes a significant reorganization of the standard library, with several historically bundled gems being promoted to "core" status, and others being rewritten in C for performance.[17]

6.1 Pathname: From Stdlib to Core

The Pathname class, which provides an object-oriented interface for file paths, has been moved from the standard library into Ruby's core. This change reflects its ubiquity: virtually every Ruby application uses Pathname for file manipulation.[4]

By moving Pathname to core, the Ruby team ensures that it is always available without requiring an explicit require statement. This reduces boilerplate and eliminates a common source of confusion for beginners.[13]

6.2 Set: Rewritten in C

The Set class, a collection type that guarantees uniqueness, has been rewritten as a C extension. The previous pure-Ruby implementation was elegant but slow, particularly for large sets. The new C implementation is 5-10x faster for common operations like union, intersection, and membership testing.[4]

This rewrite is part of a broader trend: as Ruby's performance-critical libraries are identified through profiling data, the core team is selectively rewriting them in C or Rust to eliminate hotspots.[23]

6.3 Deprecations and Removals

Several long-deprecated libraries have been removed in Ruby 4.0:

  • DRb (Distributed Ruby): The distributed object framework, largely unused in modern applications, has been moved to a separate gem.[17]
  • Fiddle: The low-level FFI (Foreign Function Interface) library remains but is no longer bundled by default.[4]
  • OpenStruct: The dynamic struct class, which was a source of performance issues, has been replaced by the faster Data class.[13]

These removals reduce the maintenance burden on the core team and encourage the community to adopt more modern alternatives.


7. The Philosophical Shift: Ruby as a Systems Language

Ruby 4.0 represents more than technical improvements; it signals a philosophical shift in how Ruby positions itself in the programming language ecosystem.

7.1 From Scripting to Systems

Ruby emerged in the 1990s as a "scripting language"—a term that implied quick, disposable code for automation tasks. The success of Rails cemented this perception: Ruby was the language of rapid web development, not high-performance systems.[1]

Ruby 4.0 challenges this narrative. With ZJIT's SSA-based compilation, Ractor's parallel execution, and Ruby::Box's process-like isolation, Ruby now possesses the primitives required for systems programming. It is becoming feasible to write:

  • High-performance data processing pipelines (using Ractor::Port)
  • Multi-tenant SaaS platforms (using Ruby::Box)
  • Compilers and interpreters (using Prism as a parsing library)
  • Embedded systems (using WebAssembly-compiled Prism)[1]

These use cases were previously the domain of languages like Go, Rust, or Java. Ruby 4.0 positions itself as a viable alternative, particularly for teams that value developer ergonomics and rapid iteration.[23]

7.2 The "Ruby 4.0" Era

Ruby 4.0 is not an endpoint but a foundation. The introduction of Ruby::Box and ZJIT provides the structural capacity for Ruby to scale to larger codebases (via isolation) and higher performance tiers (via SSA-based compilation). It refutes the narrative of Ruby's decline, offering a roadmap where the language becomes more modular, more concurrent, and more accessible to systems programmers.

For the professional Rubyist, version 4.0 is an invitation to rethink architectural patterns. It moves the language beyond the "Rails Monolith" archetype, offering the primitives to build modular, concurrent systems within a single, coherent process. Thirty years in, Ruby is not just celebrating its past; it is aggressively compiling its future.


8. Summary of Technical Specifications

The following table summarizes the key architectural shifts between Ruby 3.x and Ruby 4.0:

Feature Area Ruby 3.x Strategy Ruby 4.0 Strategy Key Technologies
Compilation YJIT (Lazy Basic Block) Multi-Tier: YJIT (Prod) + ZJIT (Exp) Rust, SSA, HIR, LBBV
Isolation Refinements (Lexical) Boxes (Runtime Container) Ruby::Box, Namespace Copying
Concurrency Ractor 1.0 (Identity-based) Ractor 2.0 (Channel-based) Ractor::Port, Lock-free Structures
Parsing Fragmentation (Ripper/Bison) Unification (Prism) Prism (YARP), Error Tolerance
Dependencies Global Namespace Conflict Isolated Dependency Loading Ruby::Box
Core Libs Pathname (Stdlib), Set (Ruby) Pathname (Core), Set (Native) C-Extension optimizations

9. Conclusion

Ruby 4.0 fulfills the promise of a modern, multi-paradigm language. It balances the human-centric design that Matz prioritized thirty years ago with the machine-centric requirements of modern infrastructure. By solving the hard problems of global state (Ruby::Box), compiler accessibility (ZJIT), and parser unification (Prism), Ruby 4.0 ensures that the language remains not just a "beautiful" choice for developers, but a "smart" choice for architects. The "Christmas Release" of 2025 is a gift of longevity, ensuring Ruby's relevance for decades to come.


Bibliografia

  1. Ruby Turns 30: A Celebration of Code, Community, and Creativity | The RubyMine Blog, accesso eseguito il giorno dicembre 31, 2025, https://blog.jetbrains.com/ruby/2025/12/ruby-turns-30-a-celebration-of-code-community-and-creativity/

  2. What Is New In Ruby 4.0 - Saeloun Blog, accesso eseguito il giorno dicembre 31, 2025, https://blog.saeloun.com/2025/12/24/what-is-new-in-ruby-4/

  3. Ruby 4.0.0 Released, accesso eseguito il giorno dicembre 31, 2025, https://www.ruby-lang.org/en/news/2025/12/25/ruby-4-0-0-released/

  4. Ruby 4.0 changes, accesso eseguito il giorno dicembre 31, 2025, https://rubyreferences.github.io/rubychanges/4.0.html

  5. NEWS - Documentation for Ruby 4.0 : r/ruby - Reddit, accesso eseguito il giorno dicembre 31, 2025, https://www.reddit.com/r/ruby/comments/1owp9o4/news_documentation_for_ruby_40/

  6. Ruby 4.0.0 preview3 Released, accesso eseguito il giorno dicembre 31, 2025, https://www.ruby-lang.org/en/news/2025/12/18/ruby-4-0-0-preview3-released/

  7. ZJIT has been merged into Ruby | Rails at Scale, accesso eseguito il giorno dicembre 31, 2025, https://railsatscale.com/2025-05-14-merge-zjit/

  8. ZJIT is now available in Ruby 4.0 - Rails at Scale, accesso eseguito il giorno dicembre 31, 2025, https://railsatscale.com/2025-12-24-launch-zjit/

  9. Ruby::Box Digest Introduction (Ruby 4.0.0 New Feature) - DEV Community, accesso eseguito il giorno dicembre 31, 2025, https://dev.to/ko1/rubybox-digest-introduction-ruby-400-new-feature-3bch

  10. Organizing monkey patches - ruby - Stack Overflow, accesso eseguito il giorno dicembre 31, 2025, https://stackoverflow.com/questions/41497350/organizing-monkey-patches

  11. Refinement: The Correct Way To Monkey-Patch in Ruby | by reinteractive - Medium, accesso eseguito il giorno dicembre 31, 2025, https://medium.com/@reinteractivehq/refinement-the-correct-way-to-monkey-patch-in-ruby-11d5141d4e72

  12. box - Documentation for Ruby 4.0 - doc.ruby-lang.org, accesso eseguito il giorno dicembre 31, 2025, https://docs.ruby-lang.org/en/master/language/box_md.html

  13. What's new in Ruby 4.0 - The Miners, accesso eseguito il giorno dicembre 31, 2025, https://blog.codeminer42.com/whats-new-in-ruby-4-0/

  14. Ruby 4.0 introduces Ruby::Box — isolated execution without extra processes - Reddit, accesso eseguito il giorno dicembre 31, 2025, https://www.reddit.com/r/rails/comments/1pxorfn/ruby_40_introduces_rubybox_isolated_execution/

  15. class Ruby::Box - Documentation for Ruby 4.1, accesso eseguito il giorno dicembre 31, 2025, https://docs.ruby-lang.org/en/master/Ruby/Box.html

  16. Port` - Revamping the Ractor API - DEV Community, accesso eseguito il giorno dicembre 31, 2025, https://dev.to/ko1/ractorport-revamping-the-ractor-api-98

  17. Changes/Ruby 4.0 - Fedora Project Wiki, accesso eseguito il giorno dicembre 31, 2025, https://fedoraproject.org/wiki/Changes/Ruby_4.0

  18. module Prism - Documentation for Ruby 4.0, accesso eseguito il giorno dicembre 31, 2025, https://docs.ruby-lang.org/en/master/Prism.html

  19. Ruby's New Parser: Why Prism Is the Future of Ruby Development | Super Good Software, accesso eseguito il giorno dicembre 31, 2025, https://supergood.software/rubys-new-prism-parser/

  20. Require prism as a parser to analyse newly released ruby versions #13617 - GitHub, accesso eseguito il giorno dicembre 31, 2025, https://github.com/rubocop/rubocop/issues/13617

  21. Prism - Kevin Newton, accesso eseguito il giorno dicembre 31, 2025, https://kddnewton.com/2024/01/23/prism.html

  22. CRuby switches the default parser from parse.y to Prism : r/ruby - Reddit, accesso eseguito il giorno dicembre 31, 2025, https://www.reddit.com/r/ruby/comments/1fjjbft/cruby_switches_the_default_parser_from_parsey_to/

  23. Optimizing Ruby performance: Observations from thousands of real-world services, accesso eseguito il giorno dicembre 31, 2025, https://www.datadoghq.com/blog/ruby-performance-optimization/

Top comments (0)