[Ruby] Ruby 3.2.0 发布

  • Ruby 3.2.0 发布

原文链接 Ruby 3.2.0 Released - https://www.ruby-lang.org/en/news/2022/12/25/ruby-3-2-0-released/

由 naruse 发表于 2022 年 12 月 25 日

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

基于 WASI 的 WebAssembly 支持

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

背景

WebAssembly (Wasm)最初是为了在网络浏览器中安全快速地运行程序而引入的。但它的目标——在各种环境中高效、安全地运行程序——不仅是 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 应用程序的分发更容易一些。

相关链接

添加基于 WASI 的 WebAssembly 支持 #5407 - https://github.com/ruby/ruby/pull/5407

Ruby 中对 WebAssembly/WASI 支持的更新 - https://itnext.io/final-report-webassembly-wasi-support-in-ruby-4aface7d90c9

生产就绪的 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+。[功能#18481]

    • 为了确保 CRuby 是使用 YJIT 构建的,请rustc在运行脚本之前安装 >= 1.58.0 ./configure。

    • 如果您遇到任何问题,请联系 YJIT 团队。

  • YJIT 3.2 版本比 3.1 更快,内存开销大约是 3.1 的 1/3。

    • 总体而言,YJIT 比yjit-bench上的 Ruby 解释器快 41%(几何平均值)。

    • JIT 代码的物理内存是延迟分配的。与 Ruby 3.1 不同,Ruby 进程的 RSS 被最小化,因为分配的虚拟内存页–yjit-exec-mem-size在 JIT 代码实际使用之前不会映射到物理内存页。

    • 引入 Code GC,当 JIT 代码的内存消耗达到–yjit-exec-mem-size.

    • RubyVM::YJIT.runtime_stats除了现有的inline_code_size和outlined_code_size键之外,还返回代码 GC 指标: code_gc_count、live_page_count、freed_page_count和freed_code_size。

  • RubyVM::YJIT.runtime_stats 生成的大部分统计数据现在都可以在发布版本中使用。

    • 只需运行 ruby–yjit-stats​​ 来计算和转储统计信息(会产生一些运行时开销)。
  • YJIT 现在经过优化以利用对象形状。[功能#18776]

  • 在定义新常量时,利用更细粒度的常量失效来使更少的代码无效。[功能#18589]

  • 默认–yjit-exec-mem-size值更改为 64 (MiB)。

  • 默认–yjit-call-threshold值更改为 30。

针对 ReDoS 的正则表达式改进

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

我们引入了两项可显着缓解 ReDoS 攻击的改进。

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

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

1
2
3
# 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 倍的内存输入长度。如果在实际应用程序中匹配正则表达式时内存不足,请报告。)

最初的提议是https://bugs.ruby-lang.org/issues/19104

正则表达式超时

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

1
2
3
4
Regexp.timeout = 1.0

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

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

1
2
3
4
5
6
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。

其他值得注意的新功能

语法建议

  • syntax_suggest(以前)的功能dead_end已集成到 Ruby 中。这可以帮助您找到错误的位置,例如丢失或多余end的 s,让您更快地回到正轨,例如以下示例:

    1
    2
    3
    4
    5
    6
    Unmatched `end', missing keyword (`do', `def`, `if`, etc.) ?

    1 class Dog
    > 2 defbark
    > 3 end
    4 end

    [功能 #18159]

错误高亮

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

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

语言

  • 匿名 rest 和关键字 rest 参数现在可以作为参数传递,而不仅仅是在方法参数中使用。[功能#18351]
1
2
3
4
5
6
def foo(*)
bar(*)
end
def baz(**)
quux(**)
end
  • 接受单个位置参数和关键字的 proc 将不再 autosplat。[错误#18633]
1
2
3
4
5
proc{|a, **k| a}.call([1, 2])
# Ruby 3.1 and before
# => 1
# Ruby 3.2 and after
# => [1, 2]
  • 在显式对象上赋值的常量的常量赋值运行顺序已与单个属性赋值运行顺序一致。使用此代码:
1
foo::BAR = baz
  • foo 现在在 baz 之前运行。类似地,对于对常量的多次赋值,使用从左到右的运行顺序。使用此代码:
1
foo1::BAR1, foo2::BAR2 = baz1, baz2
  • 现在使用以下运行顺序:
1
2
3
4
foo1
foo2
baz1
baz2

[错误#15928]

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

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

[错误#18625] [错误#16466]

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
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 编译器在派生的 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。

    • pubPubGrub 是 Dart 编程语言的包管理器使用的下一代求解算法。

    • 此更改后您可能会得到不同的分辨率结果。请将此类情况报告给 RubyGems/Bundler issues

  • RubyGems 在 Ruby 3.2 中仍然使用 Molinillo 解析器。我们计划在未来将其替换为 PubGrub。

自 3.1 以来的其他显着变化

数据

  • 新的核心类来表示简单的不可变值对象。该类类似于 Struct 并部分共享一个实现,但具有更精简和严格的 API。[功能#16122]
1
2
3
4
5
6
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#shift 如果哈希为空,现在总是返回 nil,而不是返回默认值或调用默认过程。[错误#16908]
  • 匹配数据

    • MatchData#byteoffset 已添加。[功能#13110]
  • 模块

    • Module.used_refinements 已添加。[功能#14332]

    • Module#refinements 已添加。[功能#12737]

    • Module#const_added 已添加。[功能#17881]

  • 过程

    • Proc#dup 返回子类的实例。[错误#17545]

    • Proc#parameters 现在接受 lambda 关键字。[功能#15357]

  • 求精

    • Refinement#refined_class 已添加。[功能#12737]
  • RubyVM::抽象语法树

    • 添加 error_tolerant 选项 parse,parse_file 和 of。[功能 #19013] 使用此选项

        1. SyntaxError 被抑制
        1. AST 返回无效输入
        1. end当解析器到达输入末尾但end不够时被补充
        1. end被视为基于缩进的关键字
        1
        2
        3
        4
        5
        6
        7
        8
        9
        10
        11
        12
        13
        14
        15
        16
        17
        18
        19
        20
        21
        22
        23
        24
        25
        26
        27
        28
        29
        30
        31
        # 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>]
      • 添加 keep_tokens 选项 parse, parse_file 和 of。[功能 #19070]

        1
        2
        3
        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”. [功能 #16989] 当前通过 Set 常量或调用自动加载 Enumerable#to_set。
  • String

    • String#byteindex 并 String#byterindex 已添加。[功能#13110]

    • 将 Unicode 更新到版本 15.0.0 和表情符号版本 15.0。[功能 #18639](也适用于正则表达式)

    • String#bytesplice 已添加。[功能#18598]

  • 结构体

    • 也可以使用关键字参数初始化 Struct 类而不 keyword_init: true 使用 Struct.new [功能 #16806]

      1
      2
      3
      4
      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">

兼容性问题

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

删除常量

删除了以下不推荐使用的常量。

  • Fixnum 和 Bignum [功能 #12005]

  • Random::DEFAULT [功能#17351]

  • Struct::Group

  • Struct::Passwd

删除的方法

删除了以下弃用的方法。

  • Dir.exists? [功能#17391]

  • File.exists? [功能#17391]

  • Kernel#=~ [功能#15231]

  • Kernel#taint, Kernel#untaint, Kernel#tainted? [功能 #16131]

  • Kernel#trust, Kernel#untrust, Kernel#untrusted? [功能 #16131]

标准库兼容性问题

不再捆绑第三方资源

  • 我们不再捆绑 3rd 方资源,如 libyaml, libffi。

    • libyaml 源已从 psych 中删除。您可能需要 libyaml-dev 使用 Ubuntu/Debian 平台进行安装。每个平台的包名称都不同。

    • 捆绑的 libffi 源也从 fiddle

  • Psych 和 fiddle 支持使用特定版本的 libyaml 和 libffi 源进行静态构建。您可以像这样使用 libyaml-0.2.5 构建 psych:

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

    您可以像这样使用 libffi-3.4.4 构建小提琴:

    1
    $ ./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]

标准库更新

  • Bundler

    • 添加 –ext=rust 支持内置 gem,以创建带有 Rust 扩展的简单 gem。[GH-rubygems-6149]

    • 使克隆 git repos 更快 [GH-rubygems-4475]

  • RubyGems

    • 添加对 cargo builder 的 mswin 支持。[GH-rubygems-6167]

ERB

  • ERB::Util.html_escape 比 CGI.escapeHTML.

    • 当不需要转义字符时,它不再分配 String 对象。

    • #to_s 当参数已经是字符串时,它会跳过调用方法。

    • ERB::Escape.html_escape 作为别名添加到 ERB::Util.html_escape,它还没有被 Rails 修补。

IRB

  • 已添加 debug.gem 集成命令:debug, break, catch, next, delete, step, continue, finish, backtrace, info

    • gem “debug” 即使您的 Gemfile 中没有,它们也能正常工作。

    • 另请参阅:Ruby 3.2 的 IRB 中有哪些新功能?

  • 添加了更多类似 Pry 的命令和功能。

    • edit 和 show_cmds(如 Pry 的 help )被添加。

    • lstakes-g 或 -Goption 来过滤掉输出。

    • 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 的详细信息,请参阅 GitHub 版本,例如 GitHub Releases of logger 或 changelog。

有关详细信息,请参阅新闻 或提交日志。

通过这些更改, 自 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

1
2
3
4
SIZE: 20440715
SHA1: fb4ab2ceba8bf6a5b9bc7bf7cac945cc94f94c2b
SHA256: daaa78e1360b2783f98deeceb677ad900f3a36c0ffa6e2b6b19090be77abc272
SHA512: 94203051d20475b95a66660016721a0457d7ea57656a9f16cdd4264d8aa6c4cd8ea2fab659082611bfbd7b00ebbcf0391e883e2ebf384e4fab91869e0a877d35

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

1
2
3
4
SIZE: 15058364
SHA1: bcdae07183d66fd902cb7bf995545a472d2fefea
SHA256: d2f4577306e6dd932259693233141e5c3ec13622c95b75996541b8d5b68b28b4
SHA512: 733ecc6709470ee16916deeece9af1c76220ae95d17b2681116aff7f381d99bc3124b1b11b1c2336b2b29e468e91b90f158d5ae5fca810c6cf32a0b6234ae08e

https://cache.ruby-lang.org/pub/ruby/3.2/ruby-3.2.0.zip

1
2
3
4
SIZE: 24583271
SHA1: 581ec7b9289c2a85abf4f41c93993ecaa5cf43a5
SHA256: cca9ddbc958431ff77f61948cb67afa569f01f99c9389d2bbedfa92986c9ef09
SHA512: b7d2753825cc0667e8bb391fc7ec59a53c3db5fa314e38eee74b6511890b585ac7515baa2ddac09e2c6b6c42b9221c82e040af5b39c73e980fbd3b1bc622c99d

什么是 Ruby

Ruby 最早由 Matz(Yukihiro Matsumoto)于 1993 年开发,现在作为 Open Source 开发。它在多个平台上运行,并在世界范围内广泛使用,尤其是用于 Web 开发。

参考链接

[1] Ruby 3.2.0 Released - https://www.ruby-lang.org/en/news/2022/12/25/ruby-3-2-0-released/

[2] Ruby Programming Language - https://www.ruby-lang.org/en/