由 naruse 于 2022 年 12 月 25 日发布
我们很高兴地宣布 Ruby 3.2.0 的发布。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 填补了这一空白,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+。[特性 #18481]
- 为了确保 CRuby 使用 YJIT 构建,请在运行
./configure
脚本之前安装rustc
>= 1.58.0。 - 如果您遇到任何问题,请联系 YJIT 团队。
- 为了确保 CRuby 使用 YJIT 构建,请在运行
- YJIT 3.2 版本比 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_size
和outlined_code_size
键之外,还返回代码 GC 指标:code_gc_count
、live_page_count
、freed_page_count
和freed_code_size
。
- 现在,由
RubyVM::YJIT.runtime_stats
生成的大部分统计信息都可以在发布版本中使用。- 只需运行带有
--yjit-stats
的 ruby 即可计算并转储统计信息(会产生一些运行时开销)。
- 只需运行带有
- YJIT 现在经过优化,可以利用对象形状。[特性 #18776]
- 利用更细粒度的常量失效,在定义新常量时失效更少的代码。[特性 #18589]
- 默认的
--yjit-exec-mem-size
已更改为 64 (MiB)。 - 默认的
--yjit-call-threshold
已更改为 30。
针对 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 > 3 end 4 end
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 不再自动展开。[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
现在在
baz
之前调用foo
。类似地,对于多个常量赋值,使用从左到右的评估顺序。使用此代码foo1::BAR1, foo2::BAR2 = baz1, baz2
现在使用以下评估顺序
foo1
foo2
baz1
baz2
-
find 模式不再是实验性的。[特性 #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 编译器在 forked 进程下执行,而不是在名为 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 问题
- PubGrub 是 Dart 编程语言的
-
RubyGems 在 Ruby 3.2 中仍使用 Molinillo 解析器。我们计划将来将其替换为 PubGrub。
自 3.1 以来的其他值得注意的更改
- Data
-
新的核心类,用于表示简单的不可变值对象。该类类似于 Struct 并部分共享实现,但具有更精简和严格的 API。[特性 #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#shift
现在始终返回 nil,而不是返回默认值或调用默认 proc。[Bug #16908]
- 如果哈希为空,
- MatchData
- 添加了
MatchData#byteoffset
。[特性 #13110]
- 添加了
- Module
- Proc
Proc#dup
返回子类的实例。[Bug #17545]Proc#parameters
现在接受 lambda 关键字。[特性 #15357]
- Refinement
- 添加了
Refinement#refined_class
。[特性 #12737]
- 添加了
- RubyVM::AbstractSyntaxTree
- 为
parse
、parse_file
和of
添加了error_tolerant
选项。[特性 #19013] 使用此选项- SyntaxError 被抑制
- 为无效输入返回 AST
- 当解析器到达输入末尾但
end
不足以满足时,end
会被补充。 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_file
和of
添加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 可以作为内置类使用,无需
require "set"
。[Feature #16989] 它目前通过Set
常量或调用Enumerable#to_set
进行自动加载。
- 现在,Set 可以作为内置类使用,无需
- 字符串
- 添加了
String#byteindex
和String#byterindex
。[Feature #13110] - 将 Unicode 更新到 15.0.0 版本,Emoji 更新到 15.0 版本。[Feature #18639] (也适用于正则表达式)
- 添加了
String#bytesplice
。[Feature #18598]
- 添加了
- 结构体
-
一个 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">
-
兼容性问题
注意:不包括功能错误修复。
移除的常量
移除了以下已弃用的常量。
Fixnum
和Bignum
[Feature #12005]Random::DEFAULT
[Feature #17351]Struct::Group
Struct::Passwd
移除的方法
移除了以下已弃用的方法。
Dir.exists?
[Feature #17391]File.exists?
[Feature #17391]Kernel#=~
[Feature #15231]Kernel#taint
,Kernel#untaint
,Kernel#tainted?
[Feature #16131]Kernel#trust
,Kernel#untrust
,Kernel#untrusted?
[Feature #16131]
标准库兼容性问题
不再捆绑第三方源代码
-
我们不再捆绑第三方源代码,如
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
C API 更新
更新的 C API
以下 API 已更新。
- PRNG 更新
rb_random_interface_t
已更新并版本化。使用此接口并为旧版本构建的扩展库。还需要定义init_int32
函数。
移除的 C API
移除了以下已弃用的 API。
rb_cData
变量。- “污染”和“信任”函数。[Feature #16131]
标准库更新
-
Bundler
- 为 bundle gem 添加 –ext=rust 支持,以便创建带有 Rust 扩展的简单 gem。[GH-rubygems-6149]
- 使克隆 git 仓库更快。[GH-rubygems-4475]
-
RubyGems
- 为 cargo 构建器添加 mswin 支持。[GH-rubygems-6167]
-
ERB
ERB::Util.html_escape
比CGI.escapeHTML
更快。- 当没有字符需要转义时,它不再分配 String 对象。
- 当参数已经是 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
- 即使您的 Gemfile 中没有
gem "debug"
,它们也能工作。 - 另请参阅:Ruby 3.2 的 IRB 有哪些新功能?
- 即使您的 Gemfile 中没有
- 添加了更多类似 Pry 的命令和功能。
- 添加了
edit
和show_cmds
(类似于 Pry 的help
)。 ls
接受-g
或-G
选项来过滤输出。show_source
从$
别名而来,并接受不带引号的输入。whereami
从@
别名而来。
- 添加了
- 添加了 debug.gem 集成命令:
-
更新了以下默认 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 版本(如 logger 的 GitHub 版本)或更改日志。
有关更多详细信息,请参阅 NEWS 或 commit 日志。
自 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 开发中。