Ruby 3.2.0 Preview 3 发布

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

基于 WASI 的 WebAssembly 支持

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

背景

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

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

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

用例

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

技术要点

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

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

相关链接

针对 ReDoS 的正则表达式改进

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

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

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

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

# This matching takes 10 sec. in Ruby 3.1, and does 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

其他值得注意的新功能

不再捆绑第三方源代码

  • 我们不再捆绑 libyaml, libffi 等第三方源代码。

    • libyaml 源代码已从 psych 中删除。你可能需要在 Ubuntu/Debian 平台上安装 libyaml-dev。包名称在每个平台上都不同。

    • 捆绑的 libffi 源代码也已从 fiddle 中删除

语言

  • 匿名 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

  • 支持 UNIX 平台上的 arm64 / aarch64。
  • 构建 YJIT 需要 Rust 1.58.1+。 [功能 #18481]

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

  • 哈希
    • 如果哈希为空,Hash#shift 现在总是返回 nil,而不是返回默认值或调用默认 proc。 [错误 #16908]
  • MatchData
  • 模块
  • Proc
  • 精炼
  • RubyVM::AbstractSyntaxTree
    • parseparse_fileof 添加了 error_tolerant 选项。 [功能 #19013]
  • 集合
    • Set 现在作为一个内置类可用,无需 require "set"。 [功能 #16989] 它目前通过 Set 常量或调用 Enumerable#to_set 来自动加载。
  • 字符串
    • 添加了 String#byteindex 和 String#byterindex。 [功能 #13110]
    • 将 Unicode 更新到 14.0.0 版本,并将 Emoji 更新到 14.0 版本。 [功能 #18037] (也适用于 Regexp)
    • 添加了 String#bytesplice。 [功能 #18598]
  • 结构体
    • 一个 Struct 类也可以在 Struct.new 上没有 keyword_init: true 的情况下使用关键字参数进行初始化 [功能 #16806]

兼容性问题

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

已删除的常量

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

已删除的方法

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

Stdlib 兼容性问题

  • Psych 不再捆绑 libyaml 源代码。用户需要通过包系统自行安装 libyaml 库。 [功能 #18571]

C API 更新

更新的 C API

更新了以下 API。

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

已删除的 C API

删除了以下已弃用的 API。

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

标准库更新

  • SyntaxSuggest

    • 以前的 dead_endsyntax_suggest 功能已集成到 Ruby 中。 [功能 #18159]
  • ErrorHighlight

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

sum = ary[0] + ary[1]
               ^^^^^^
  • 更新了以下默认 gem。
    • RubyGems 3.4.0.dev
    • bigdecimal 3.1.2
    • bundler 2.4.0.dev
    • cgi 0.3.2
    • date 3.2.3
    • error_highlight 0.4.0
    • etc 1.4.0
    • io-console 0.5.11
    • io-nonblock 0.1.1
    • io-wait 0.3.0.pre
    • ipaddr 1.2.4
    • json 2.6.2
    • logger 1.5.1
    • net-http 0.2.2
    • net-protocol 0.1.3
    • ostruct 0.5.5
    • psych 5.0.0.dev
    • reline 0.3.1
    • securerandom 0.2.0
    • set 1.0.3
    • stringio 3.0.3
    • syntax_suggest 0.0.1
    • timeout 0.3.0
  • 更新了以下捆绑的 gem。
    • minitest 5.16.3
    • net-imap 0.2.3
    • rbs 2.6.0
    • typeprof 0.21.3
    • debug 1.6.2
  • 以下默认 gem 现在是捆绑的 gem。

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

通过这些更改,自 Ruby 3.1.0 以来,更改了 2719 个文件,插入了 191269 行代码 (+),删除了 120315 行代码 (-)

下载

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

    SIZE: 20086542
    SHA1: dafca8116d36ceaa32482ab38359768de8c3ae5e
    SHA256: c041d1488e62730d3a10dbe7cf7a3b3e4268dc867ec20ec991e7d16146640487
    SHA512: 860634d95e4b9c48f18d38146dfbdc3c389666d45454248a4ccdfc3a5d3cd0c71c73533aabf359558117de9add1472af228d8eaec989c9336b1a3a6f03f1ae88
    
  • https://cache.ruby-lang.org/pub/ruby/3.2/ruby-3.2.0-preview3.tar.xz

    SIZE: 14799804
    SHA1: c94e2add05502cb5c39afffc995b7c8f000f7df0
    SHA256: d3f5619de544240d92a5d03aa289e71bd1103379622c523a0e80ed029a74b3bb
    SHA512: c1864e2e07c3711eaa17d0f85dfbcc6e0682b077782bb1c155315af45139ae66dc4567c73682d326975b0f472111eb0a70f949811cb54bed0b3a816ed6ac34df
    
  • https://cache.ruby-lang.org/pub/ruby/3.2/ruby-3.2.0-preview3.zip

    SIZE: 24426893
    SHA1: 346c051c4be7ab8d0b551fd2ff8169785697db62
    SHA256: cf49aa70e7ebd8abebffd5e49cd3bd92e5b9f3782d587cc7ed88c98dd5f17069
    SHA512: 4f22b5ea91be17ef5f68cf0acb1e3a226dcc549ad71cc9b40e623220087c4065ca9bea942710f668e5c94ca0323da8d2ccd565f95a9085c1a0e38e9c0543b22f
    

什么是 Ruby

Ruby 最初由 Matz(松本行弘)于 1993 年开发,现在以开源形式进行开发。它可在多个平台上运行,并在全球范围内使用,尤其是在 Web 开发领域。