Ruby 3.2.0 RC 1 发布

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

基于 WASI 的 WebAssembly 支持

这是基于 WASI 的 WebAssembly 支持的初始端口。这使得 CRuby 二进制文件可以在 Web 浏览器、无服务器边缘环境或其他类型的 WebAssembly/WASI 嵌入器上使用。目前,此端口通过了不使用 Thread API 的基本和引导测试套件。

背景

WebAssembly (Wasm) 最初是为了在 Web 浏览器中安全快速地运行程序而引入的。但是它的目标——在各种环境中安全高效地运行程序——不仅是 Web 所需的,也是一般应用程序所需要的。

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

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

用例

此支持鼓励开发人员在 WebAssembly 环境中使用 CRuby。一个示例用例是 TryRuby Playground 的 CRuby 支持。现在您可以在 Web 浏览器中尝试原始的 CRuby。

技术要点

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

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

相关链接

针对 ReDoS 的正则表达式改进

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

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

改进的正则表达式匹配算法

自 Ruby 3.2 以来,通过使用记忆化技术,正则表达式的匹配算法得到了极大的改进。

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

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

改进后的匹配算法允许大多数正则表达式匹配(在我们的实验中约为 90%)在线性时间内完成。

(对于预览用户:此优化可能会为每次匹配消耗与输入长度成正比的内存。我们预计不会出现实际问题,因为此内存分配通常会延迟,并且正常的正则表达式匹配应最多消耗输入长度 10 倍的内存。如果您在实际应用程序中匹配正则表达式时内存不足,请报告。)

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

正则表达式超时

上述优化不能应用于某些类型的正则表达式,例如那些包含高级功能(例如,反向引用或环视)或具有大量固定重复次数的正则表达式。作为后备措施,还引入了正则表达式匹配的超时功能。

Regexp.timeout = 1.0

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

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

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
    > 4    end
      5  end
    

    [功能 #18159]

ErrorHighlight

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

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

语言

  • 匿名 rest 和 keyword rest 参数现在可以作为参数传递,而不仅仅是在方法参数中使用。 [功能 #18351]

      def foo(*)
        bar(*)
      end
      def baz(**)
        quux(**)
      end
    
  • 接受单个位置参数和关键字的 proc 将不再自动展开。 [错误 #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

    [错误 #15928]

  • 查找模式不再是实验性的。 [功能 #18585]

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

性能改进

YJIT

  • YJIT 现在支持 Linux、MacOS、BSD 和其他 UNIX 平台上的 x86-64 和 arm64/aarch64 CPU。
    • 此版本为 Mac M1/M2、AWS Graviton 和 Raspberry Pi 4 ARM64 处理器提供支持。
  • 构建 YJIT 需要 Rust 1.58.0+。 [功能 #18481]
    • 为了确保使用 YJIT 构建 CRuby,请安装 rustc >= 1.58.0 并运行 ./configure--enable-yjit
    • 如果您遇到任何问题,请联系 YJIT 团队。
  • JIT 代码的物理内存是延迟分配的。与 Ruby 3.1 不同,Ruby 进程的 RSS 被最小化,因为 --yjit-exec-mem-size 分配的虚拟内存页在实际被 JIT 代码使用之前不会映射到物理内存页。
  • 引入代码 GC,当 JIT 代码的内存消耗达到 --yjit-exec-mem-size 时,会释放所有代码页。
    • 除了现有的 inline_code_sizeoutlined_code_size 键之外,RubyVM::YJIT.runtime_stats 还返回代码 GC 指标:code_gc_countlive_page_countfreed_page_countfreed_code_size
  • RubyVM::YJIT.runtime_stats 生成的大多数统计信息现在都可以在发布版本中使用。
    • 只需运行带有 --yjit-stats 的 ruby 即可计算统计信息(会产生一些运行时开销)。
  • YJIT 现在经过优化,可以利用对象形状。 [功能 #18776]
  • 利用更细粒度的常量失效,以便在定义新常量时使更少的代码失效。 [功能 #18589]

MJIT

  • MJIT 编译器在 Ruby 中作为标准库 mjit 重新实现。
  • MJIT 编译器在 forked Ruby 进程下执行,而不是在名为 MJIT worker 的本机线程中执行。[[功能 #18968]]
    • 因此,不再支持 Microsoft Visual Studio (MSWIN)。
  • 不再支持 MinGW。 [[功能 #18824]]
  • --mjit-min-calls 重命名为 --mjit-call-threshold
  • 将默认 --mjit-max-cache 从 10000 改回 100。

PubGrub

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

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

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

  • Hash
    • 如果哈希为空,Hash#shift 现在始终返回 nil,而不是返回默认值或调用默认 proc。 [错误 #16908]
  • MatchData
  • Module
  • Proc
  • Refinement
  • RubyVM::AbstractSyntaxTree
    • parseparse_fileof 添加 error_tolerant 选项。 [[功能 #19013]]
  • Set
    • Set 现在可以作为内置类使用,无需 require "set"。 [功能 #16989] 它目前通过 Set 常量或调用 Enumerable#to_set 来自动加载。
  • String
    • 已添加 String#byteindex 和 String#byterindex。 [功能 #13110]
    • 将 Unicode 更新到版本 15.0.0 和 Emoji 版本 15.0。 [功能 #18639] (也适用于正则表达式)
    • 已添加 String#bytesplice。 [功能 #18598]
  • Struct
    • Struct 类也可以在 Struct.new 上没有 keyword_init: true 的情况下使用关键字参数进行初始化 [功能 #16806]

兼容性问题

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

已删除的常量

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

已删除的方法

删除了以下已弃用的方法。

标准库兼容性问题

不再捆绑第三方源码

  • 我们不再捆绑像 libyamllibffi 这样的第三方源码。

    • 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
    

    [特性 #18571]

C API 更新

更新的 C API

以下 API 已更新。

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

移除的 C API

以下已弃用的 API 已移除。

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

标准库更新

  • 以下默认 gem 已更新。
    • RubyGems 3.4.0.dev
    • benchmark 0.2.1
    • bigdecimal 3.1.3
    • bundler 2.4.0.dev
    • cgi 0.3.6
    • date 3.3.0
    • delegate 0.3.0
    • did_you_mean 1.6.2
    • digest 3.1.1
    • drb 2.1.1
    • erb 4.0.2
    • error_highlight 0.5.1
    • etc 1.4.1
    • fcntl 1.0.2
    • fiddle 1.1.1
    • fileutils 1.7.0
    • forwardable 1.3.3
    • getoptlong 0.2.0
    • io-console 0.5.11
    • io-nonblock 0.2.0
    • io-wait 0.3.0.pre
    • ipaddr 1.2.5
    • irb 1.5.1
    • json 2.6.2
    • logger 1.5.2
    • mutex_m 0.1.2
    • net-http 0.3.1
    • net-protocol 0.2.0
    • nkf 0.1.2
    • open-uri 0.3.0
    • openssl 3.1.0.pre
    • optparse 0.3.0
    • ostruct 0.5.5
    • pathname 0.2.1
    • pp 0.4.0
    • pstore 0.1.2
    • psych 5.0.0
    • racc 1.6.1
    • rdoc 6.5.0
    • reline 0.3.1
    • resolv 0.2.2
    • securerandom 0.2.1
    • set 1.0.3
    • stringio 3.0.3
    • syntax_suggest 1.0.1
    • timeout 0.3.1
    • tmpdir 0.1.3
    • tsort 0.1.1
    • un 0.2.1
    • uri 0.12.0
    • win32ole 1.8.9
    • zlib 3.0.0
  • 以下捆绑的 gem 已更新。
    • minitest 5.16.3
    • power_assert 2.0.2
    • test-unit 3.5.5
    • net-ftp 0.2.0
    • net-imap 0.3.1
    • net-pop 0.1.2
    • net-smtp 0.3.3
    • rbs 2.8.1
    • typeprof 0.21.3
    • debug 1.7.0

有关更多详细信息,请参阅 NEWS提交日志

自 Ruby 3.1.0 以来,这些更改导致 2846 个文件被更改,203950 个插入(+),127153 个删除(-)

下载

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

    SIZE: 20253652
    SHA1: 9b45af61ef1ae3c21ab88d7c9e30b80060116ac3
    SHA256: 3bb9760c1ac1b66416aaa4899809f6ccd010e57038eaaeca19a383fd56275dac
    SHA512: 798157d785ebae94cb128d3c134fa35e0e90c654972e531cb6562823042f3fb68a270226f7b1cf0c42572ef2b1488a1a3e44f88389ad2a6f9ca4b280a2a8e759
    
  • https://cache.ruby-lang.org/pub/ruby/3.2/ruby-3.2.0-rc1.tar.xz

    SIZE: 14934012
    SHA1: 5576e304786d466410f27a345dc1cb66f2c773f6
    SHA256: 0d45b3af14e84337882a2021235a091ae5dcfc0baaf31dccc479b71d96dd07bc
    SHA512: d38fcb1e09eb9984f3b2347e65ae7406129c2578d068a25d33b5b4f021ec3b567a9abe56c2acbec6d07a3c2b4bc7b485dbd330cbfbb3a96350f60a2bb94d016e
    
  • https://cache.ruby-lang.org/pub/ruby/3.2/ruby-3.2.0-rc1.zip

    SIZE: 24473024
    SHA1: 8fdc85363ce61e0b8f04da36e709d49028d04a75
    SHA256: 7ff32473be108534548e401aaa9092c37a27f73323ea4091c33901c714c87ee5
    SHA512: 07adf6a9c89fdcf420e7b131f40f2b1f4aca036aa6f28539ade26ca552f84a75e0698f77a8b774d2ea52b8c756c4982ef319bda5afa786c081a31dd9873c5ef7
    

什么是 Ruby

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