Ruby 3.2.0 发布

naruse 于 2022 年 12 月 25 日发布

我们很高兴地宣布 Ruby 3.2.0 的发布。Ruby 3.2 增加了很多新功能和性能改进。

基于 WASI 的 WebAssembly 支持

这是基于 WASI 的 WebAssembly 支持的初步移植。它使得 CRuby 二进制文件可以在 Web 浏览器、Serverless Edge 环境或其他 WebAssembly/WASI 嵌入器中使用。目前,此移植通过了不使用 Thread API 的基本和引导测试套件。

背景

WebAssembly (Wasm) 最初被引入,用于在 Web 浏览器中安全快速地运行程序。但其目标——在各种环境中高效地运行程序并确保安全——不仅对 Web,也对通用应用程序长期以来都是非常渴望的。

WASI (WebAssembly 系统接口) 就是为此类用例而设计的。虽然这类应用程序需要与操作系统通信,但 WebAssembly 运行在一个没有系统接口的虚拟机上。WASI 对其进行了标准化。

Ruby 中的 WebAssembly/WASI 支持旨在利用这些项目。它使 Ruby 开发人员能够编写在这些有前景的平台上运行的应用程序。

用例

此支持鼓励开发人员在 WebAssembly 环境中利用 CRuby。一个用例是 TryRuby playground 对 CRuby 的支持。现在你可以在浏览器中尝试原生的 CRuby 了。

技术要点

当今的 WASI 和 WebAssembly 本身缺少实现 Fiber、异常和 GC 的一些功能,因为它们仍在发展中,并且出于安全原因。因此,CRuby 通过使用 Asyncify 来弥补不足,这是一种用户空间执行控制的二进制转换技术。

此外,我们在 WASI 之上构建了 一个 VFS,以便我们可以轻松地将 Ruby 应用打包成单个 .wasm 文件。这使得 Ruby 应用的分发更加容易。

生产就绪的 YJIT

  • YJIT 不再是实验性的
    • 已在生产环境中进行了一年多的测试,并被证明非常稳定。
  • YJIT 现在支持 Linux、MacOS、BSD 和其他 UNIX 平台上的 x86-64 和 arm64/aarch64 CPU。
    • 此版本带来了对 Apple M1/M2、AWS Graviton、Raspberry Pi 4 等的支持。
  • 构建 YJIT 现在需要 Rust 1.58.0+。[Feature #18481]
    • 为了确保 CRuby 使用 YJIT 构建,请在运行 ./configure 脚本之前安装 rustc >= 1.58.0。
    • 如果您遇到任何问题,请联系 YJIT 团队。
  • YJIT 3.2 版本比 3.1 更快,内存开销大约是 3.1 的 1/3。
    • yjit-bench 上,YJIT 的整体性能比 Ruby 解释器快 41%(几何平均值)。
    • JIT 代码的物理内存是按需分配的。与 Ruby 3.1 不同,Ruby 进程的 RSS 被最小化,因为由 --yjit-exec-mem-size 分配的虚拟内存页直到 JIT 代码实际使用时才会被映射到物理内存页。
    • 引入了代码 GC,当 JIT 代码的内存消耗达到 --yjit-exec-mem-size 时,会释放所有代码页面。
    • RubyVM::YJIT.runtime_stats 除了现有的 inline_code_sizeoutlined_code_size 键之外,还会返回代码 GC 指标:code_gc_count, live_page_count, freed_page_count, 和 freed_code_size
  • RubyVM::YJIT.runtime_stats 生成的大部分统计数据现在都可以在发布版本中使用。
    • 只需运行 Ruby 并使用 --yjit-stats 即可计算并转储统计数据(会产生一些运行时开销)。
  • YJIT 现在已针对对象形状进行了优化。[Feature #18776]
  • 利用更细粒度的常量无效化,在定义新常量时可以使更少的代码失效。[Feature #18589]
  • 默认的 --yjit-exec-mem-size 已更改为 64 (MiB)。
  • 默认的 --yjit-call-threshold 已更改为 30。

Regexp 针对 ReDoS 的改进

众所周知,Regexp 匹配可能会花费意想不到的长。如果您的代码尝试将可能低效的 Regexp 与不受信任的输入进行匹配,攻击者可能会利用它进行有效的拒绝服务攻击(所谓的正则表达式 DoS,或 ReDoS)。

我们引入了两项改进,可以显著缓解 ReDoS。

改进的 Regexp 匹配算法

从 Ruby 3.2 开始,通过使用记忆化技术,Regexp 的匹配算法得到了极大的改进。

# This match takes 10 sec. in Ruby 3.1, and 0.003 sec. in Ruby 3.2

/^a*b?a*$/ =~ "a" * 50000 + "x"

改进的匹配算法允许大多数 Regexp 匹配(在我们实验中约占 90%)以线性时间完成。

此优化可能会为每次匹配消耗与输入长度成比例的内存。我们预计不会出现实际问题,因为此内存分配通常是延迟的,并且正常的 Regexp 匹配消耗的内存最多是输入长度的 10 倍。如果您在实际应用程序中匹配 Regexp 时遇到内存不足的问题,请进行报告。

原始提案是 https://bugs.ruby-lang.org/issues/19104

Regexp 超时

上述优化不能应用于某些类型的正则表达式,例如那些包含高级特性(例如反向引用或前瞻/后顾),或具有巨大固定重复次数的正则表达式。作为一种回退措施,还引入了 Regexp 匹配的超时功能。

Regexp.timeout = 1.0

/^a*b?a*()\1$/ =~ "a" * 50000 + "x"
#=> Regexp::TimeoutError is raised in one second

请注意,Regexp.timeout 是一个全局配置。如果您想为某些特殊 Regexp 使用不同的超时设置,您可能需要使用 Regexp.newtimeout 关键字参数。

Regexp.timeout = 1.0

# This regexp has no timeout
long_time_re = Regexp.new('^a*b?a*()\1$', timeout: Float::INFINITY)

long_time_re =~ "a" * 50000 + "x" # never interrupted

原始提案是 https://bugs.ruby-lang.org/issues/17837

其他值得注意的新特性

SyntaxSuggest

  • syntax_suggest(以前称为 dead_end)的功能已集成到 Ruby 中。它可以帮助您找到错误的位置,例如缺失或多余的 end,以便更快地解决问题,例如下面的示例:

    Unmatched `end', missing keyword (`do', `def`, `if`, etc.) ?
    
      1  class Dog
    > 2    defbark
    > 3    end
      4  end
    

    [Feature #18159]

ErrorHighlight

  • 现在,它会指向 TypeError 和 ArgumentError 的相关参数。
test.rb:2:in `+': nil can't be coerced into Integer (TypeError)

sum = ary[0] + ary[1]
               ^^^^^^

语言

  • 匿名 rest 参数和关键字 rest 参数现在可以作为参数传递,而不仅仅用于方法参数。[Feature #18351]

      def foo(*)
        bar(*)
      end
      def baz(**)
        quux(**)
      end
    
  • 接受单个位置参数和关键字的 proc 将不再自动展开。[Bug #18633]

    proc{|a, **k| a}.call([1, 2])
    # Ruby 3.1 and before
    # => 1
    # Ruby 3.2 and after
    # => [1, 2]
    
  • 对显式对象设置的常量的赋值求值顺序已与单属性赋值的求值顺序一致。使用此代码:

      foo::BAR = baz
    

    现在 foo 会在 baz 之前调用。同样,对于常量的多个赋值,使用从左到右的求值顺序。使用此代码:

        foo1::BAR1, foo2::BAR2 = baz1, baz2
    

    现在使用以下求值顺序:

    1. foo1
    2. foo2
    3. baz1
    4. baz2

    [Bug #15928]

  • Find 模式不再是实验性的。[Feature #18585]

  • 接受 rest 参数(如 *args)并希望通过 foo(*args) 委托关键字参数的方法,现在必须用 ruby2_keywords 标记(如果之前没有的话)。换句话说,所有希望通过 *args 委托关键字参数的方法现在都必须用 ruby2_keywords 标记,没有例外。一旦库可以要求 Ruby 3+,这将更容易过渡到其他委托方式。之前,如果接收方法接受 *args,则会保留 ruby2_keywords 标志,但这是一个错误和不一致。查找可能缺失的 ruby2_keywords 的一个好方法是运行测试套件,找到每个测试失败的地方必须接收关键字参数的最后一个方法,并在那里使用 puts nil, caller, nil。然后检查调用链中每个必须委托关键字的方法/块是否正确标记了 ruby2_keywords。[Bug #18625] [Bug #16466]

      def target(**kw)
      end
    
      # Accidentally worked without ruby2_keywords in Ruby 2.7-3.1, ruby2_keywords
      # needed in 3.2+. Just like (*args, **kwargs) or (...) would be needed on
      # both #foo and #bar when migrating away from ruby2_keywords.
      ruby2_keywords def bar(*args)
        target(*args)
      end
    
      ruby2_keywords def foo(*args)
        bar(*args)
      end
    
      foo(k: 1)
    

性能改进

MJIT

  • MJIT 编译器已在 Ruby 中重写为 ruby_vm/mjit/compiler
  • MJIT 编译器在一个 fork 进程下执行,而不是在一个称为 MJIT worker 的原生线程中执行。[Feature #18968]
    • 因此,不再支持 Microsoft Visual Studio (MSWIN)。
  • 不再支持 MinGW。[Feature #18824]
  • --mjit-min-calls 重命名为 --mjit-call-threshold
  • 将默认的 --mjit-max-cache 从 10000 改回 100。

PubGrub

  • Bundler 2.4 现在使用 PubGrub 解析器,而不是 Molinillo

    • PubGrub 是 Dart 编程语言的 pub 包管理器使用的下一代求解算法。
    • 更改后,您可能会获得不同的解析结果。请将此类情况报告给 RubyGems/Bundler issues
  • RubyGems 仍然在 Ruby 3.2 中使用 Molinillo 解析器。我们计划将来用 PubGrub 取代它。

自 3.1 以来的其他值得注意的更改

  • Data
    • 新的核心类,用于表示简单的不可变值对象。该类类似于 Struct,并部分共享其实现,但具有更精简和更严格的 API。[Feature #16122]

        Measure = Data.define(:amount, :unit)
        distance = Measure.new(100, 'km')            #=> #<data Measure amount=100, unit="km">
        weight = Measure.new(amount: 50, unit: 'kg') #=> #<data Measure amount=50, unit="kg">
        weight.with(amount: 40)                      #=> #<data Measure amount=40, unit="kg">
        weight.amount                                #=> 50
        weight.amount = 40                           #=> NoMethodError: undefined method `amount='
      
  • Hash
    • 如果 hash 为空,Hash#shift 现在始终返回 nil,而不是返回默认值或调用默认 proc。[Bug #16908]
  • MatchData
  • Module
  • Proc
  • Refinement
  • RubyVM::AbstractSyntaxTree
    • parse, parse_fileof 添加了 error_tolerant 选项。[Feature #19013] 使用此选项:
      1. SyntaxError 被抑制
      2. 对于无效输入返回 AST
      3. 当解析器到达输入末尾但 end 不足时,将补充 end
      4. 根据缩进将 end 视为关键字。
        # Without error_tolerant option
        root = RubyVM::AbstractSyntaxTree.parse(<<~RUBY)
        def m
          a = 10
          if
        end
        RUBY
        # => <internal:ast>:33:in `parse': syntax error, unexpected `end' (SyntaxError)
      
        # With error_tolerant option
        root = RubyVM::AbstractSyntaxTree.parse(<<~RUBY, error_tolerant: true)
        def m
          a = 10
          if
        end
        RUBY
        p root # => #<RubyVM::AbstractSyntaxTree::Node:SCOPE@1:0-4:3>
      
        # `end` is treated as keyword based on indent
        root = RubyVM::AbstractSyntaxTree.parse(<<~RUBY, error_tolerant: true)
        module Z
          class Foo
            foo.
          end
      
          def bar
          end
        end
        RUBY
        p root.children[-1].children[-1].children[-1].children[-2..-1]
        # => [#<RubyVM::AbstractSyntaxTree::Node:CLASS@2:2-4:5>, #<RubyVM::AbstractSyntaxTree::Node:DEFN@6:2-7:5>]
      
    • parse, parse_fileof 添加了 keep_tokens 选项。[Feature #19070]

        root = RubyVM::AbstractSyntaxTree.parse("x = 1 + 2", keep_tokens: true)
        root.tokens # => [[0, :tIDENTIFIER, "x", [1, 0, 1, 1]], [1, :tSP, " ", [1, 1, 1, 2]], ...]
        root.tokens.map{_1[2]}.join # => "x = 1 + 2"
      
  • Set
    • Set 现在作为内置类可用,无需 require "set"。[Feature #16989] 它目前通过 Set 常量或调用 Enumerable#to_set 进行自动加载。
  • String
    • 已添加 String#byteindexString#byterindex。[Feature #13110]
    • 更新 Unicode 至 15.0.0 版本和 Emoji 15.0 版本。[Feature #18639](也适用于 Regexp)
    • 已添加 String#bytesplice。[Feature #18598]
  • Struct
    • Struct 类也可以使用关键字参数进行初始化,而无需在 Struct.new 上使用 keyword_init: true。[Feature #16806]

        Post = Struct.new(:id, :name)
        Post.new(1, "hello") #=> #<struct Post id=1, name="hello">
        # From Ruby 3.2, the following code also works without keyword_init: true.
        Post.new(id: 1, name: "hello") #=> #<struct Post id=1, name="hello">
      

兼容性问题

注意:不包括功能错误修复。

已删除的常量

已删除以下已弃用的常量。

已删除的方法

以下已弃用的方法已被移除。

标准库兼容性问题

不再捆绑第三方源

  • 我们不再捆绑第三方源,例如 libyaml, libffi

    • libyaml 源已从 psych 中移除。您可能需要在 Ubuntu/Debian 平台上安装 libyaml-dev。不同平台的包名不同。

    • 捆绑的 libffi 源也已从 fiddle 中移除。

  • Psych 和 fiddle 支持使用特定版本的 libyaml 和 libffi 源进行静态构建。您可以使用 libyaml-0.2.5 构建 psych,如下所示:

      $ ./configure --with-libyaml-source-dir=/path/to/libyaml-0.2.5
    

    您可以使用 libffi-3.4.4 构建 fiddle,如下所示:

      $ ./configure --with-libffi-source-dir=/path/to/libffi-3.4.4
    

    [Feature #18571]

C API 更新

更新的 C API

已更新以下 API。

  • PRNG 更新
    • rb_random_interface_t 已更新并版本化。使用此接口并为旧版本构建的扩展库,还需要定义 init_int32 函数。

已删除的 C API

已删除以下已弃用的 API。

  • rb_cData 变量。
  • “taintedness” 和 “trustedness” 函数。[Feature #16131]

标准库更新

  • Bundler

    • 为 Cargo Builder 添加了 –ext=rust 支持,用于创建带有 Rust 扩展的简单 gem。[GH-rubygems-6149]
    • 使克隆 git 仓库更快[GH-rubygems-4475]
  • RubyGems

  • ERB

    • ERB::Util.html_escapeCGI.escapeHTML 更快。
      • 当不需要转义任何字符时,它不再分配 String 对象。
      • 当参数已经是 String 时,它会跳过调用 #to_s 方法。
      • 已添加 ERB::Escape.html_escape 作为 ERB::Util.html_escape 的别名,后者未被 Rails monkey-patch。
  • IRB

    • 已添加 debug.gem 集成命令:debug, break, catch, next, delete, step, continue, finish, backtrace, info
    • 添加了更多类似 Pry 的命令和功能。
      • 添加了 editshow_cmds(类似于 Pry 的 help)。
      • ls 选项接受 -g-G 来过滤输出。
      • show_source$ 的别名,并接受未加引号的输入。
      • whereami@ 的别名。
  • 更新了以下默认 gem。

    • RubyGems 3.4.1
    • abbrev 0.1.1
    • benchmark 0.2.1
    • bigdecimal 3.1.3
    • bundler 2.4.1
    • cgi 0.3.6
    • csv 3.2.6
    • date 3.3.3
    • delegate 0.3.0
    • did_you_mean 1.6.3
    • digest 3.1.1
    • drb 2.1.1
    • english 0.7.2
    • erb 4.0.2
    • error_highlight 0.5.1
    • etc 1.4.2
    • fcntl 1.0.2
    • fiddle 1.1.1
    • fileutils 1.7.0
    • forwardable 1.3.3
    • getoptlong 0.2.0
    • io-console 0.6.0
    • io-nonblock 0.2.0
    • io-wait 0.3.0
    • ipaddr 1.2.5
    • irb 1.6.2
    • json 2.6.3
    • logger 1.5.3
    • mutex_m 0.1.2
    • net-http 0.3.2
    • net-protocol 0.2.1
    • nkf 0.1.2
    • open-uri 0.3.0
    • open3 0.1.2
    • openssl 3.1.0
    • optparse 0.3.1
    • ostruct 0.5.5
    • pathname 0.2.1
    • pp 0.4.0
    • pstore 0.1.2
    • psych 5.0.1
    • racc 1.6.2
    • rdoc 6.5.0
    • readline-ext 0.1.5
    • reline 0.3.2
    • resolv 0.2.2
    • resolv-replace 0.1.1
    • securerandom 0.2.2
    • set 1.0.3
    • stringio 3.0.4
    • strscan 3.0.5
    • syntax_suggest 1.0.2
    • syslog 0.1.1
    • tempfile 0.1.3
    • time 0.2.1
    • timeout 0.3.1
    • tmpdir 0.1.3
    • tsort 0.1.1
    • un 0.2.1
    • uri 0.12.0
    • weakref 0.1.2
    • win32ole 1.8.9
    • yaml 0.2.1
    • zlib 3.0.0
  • 更新了以下捆绑 gem。

    • minitest 5.16.3
    • power_assert 2.0.3
    • test-unit 3.5.7
    • net-ftp 0.2.0
    • net-imap 0.3.3
    • net-pop 0.1.2
    • net-smtp 0.3.3
    • rbs 2.8.2
    • typeprof 0.21.3
    • debug 1.7.1

有关默认 gem 或捆绑 gem 的详细信息,请参阅 logger 的 GitHub Releases 或变更日志。

有关更多详细信息,请参阅 NEWScommit 记录

通过这些更改,自 Ruby 3.1.0 以来,3048 个文件已更改,218253 个插入(+),131067 个删除(-)

圣诞快乐,节日快乐,享受 Ruby 3.2 编程的乐趣!

下载

  • https://cache.ruby-lang.org/pub/ruby/3.2/ruby-3.2.0.tar.gz

    SIZE: 20440715
    SHA1: fb4ab2ceba8bf6a5b9bc7bf7cac945cc94f94c2b
    SHA256: daaa78e1360b2783f98deeceb677ad900f3a36c0ffa6e2b6b19090be77abc272
    SHA512: 94203051d20475b95a66660016721a0457d7ea57656a9f16cdd4264d8aa6c4cd8ea2fab659082611bfbd7b00ebbcf0391e883e2ebf384e4fab91869e0a877d35
    
  • https://cache.ruby-lang.org/pub/ruby/3.2/ruby-3.2.0.tar.xz

    SIZE: 15058364
    SHA1: bcdae07183d66fd902cb7bf995545a472d2fefea
    SHA256: d2f4577306e6dd932259693233141e5c3ec13622c95b75996541b8d5b68b28b4
    SHA512: 733ecc6709470ee16916deeece9af1c76220ae95d17b2681116aff7f381d99bc3124b1b11b1c2336b2b29e468e91b90f158d5ae5fca810c6cf32a0b6234ae08e
    
  • https://cache.ruby-lang.org/pub/ruby/3.2/ruby-3.2.0.zip

    SIZE: 24583271
    SHA1: 581ec7b9289c2a85abf4f41c93993ecaa5cf43a5
    SHA256: cca9ddbc958431ff77f61948cb67afa569f01f99c9389d2bbedfa92986c9ef09
    SHA512: b7d2753825cc0667e8bb391fc7ec59a53c3db5fa314e38eee74b6511890b585ac7515baa2ddac09e2c6b6c42b9221c82e040af5b39c73e980fbd3b1bc622c99d
    

什么是 Ruby

Ruby 最初由 Matz (Yukihiro Matsumoto) 于 1993 年开发,现在作为开源项目进行开发。它运行在多个平台上,并在世界各地广泛使用,尤其是在 Web 开发领域。

近期新闻

Ruby 4.0.0 发布

我们很高兴地宣布 Ruby 4.0.0 的发布。Ruby 4.0 引入了“Ruby Box”和“ZJIT”,并增加了许多改进。

naruse 发布于 2025 年 12 月 25 日

Ruby 文档的全新外观

继 ruby-lang.org 重新设计之后,我们还有更多好消息来庆祝 Ruby 成立 30 周年:docs.ruby-lang.org 采用了 Aliki——RDoc 的新默认主题,焕然一新。

Stan Lo 发布于 2025 年 12 月 23 日

重新设计我们的网站标识

我们很高兴地宣布对我们的网站进行全面重新设计。此次更新的设计由 Akatsuka Taeko 创作。

Hiroshi SHIBATA 发布于 2025 年 12 月 22 日

Ruby 4.0.0 preview3 发布

我们很高兴地宣布 Ruby 4.0.0-preview3 的发布。Ruby 4.0 引入了 Ruby::Box 和“ZJIT”,并增加了许多改进。

naruse 发布于 2025 年 12 月 18 日

更多新闻...