Posted by naruse on 25 Dec 2022
Ruby 3.2.0 が公開されました。Ruby 3.2では多くの機能を追加するとともに、様々な改善が行われています。
WASIベースのWebAssemblyサポート
WASIベースのWebAssemblyへのコンパイルがサポートされました。これにより、ブラウザやサーバーレスエッジ環境、その他のWebAssembly/WASI環境でCRubyのバイナリが利用できるようになります。現在この移植版はThread API以外のbasic testとbootstrap testをパスしています。
Background
もともとWebAssembly (Wasm)が導入されたのは、プログラムをブラウザの上で安全かつ高速に実行するためでした。しかし、様々な環境で安全かつ効率的にプログラムを実行するという目的は、Webだけでなく一般的なアプリケーションで長らく求められていたものです
WASI (The WebAssembly System Interface)はそのようなユースケースのために設計されました。そのようなアプリケーションはOSと通信する必要がありますが、WebAssembly自体はシステムインターフェイスを持たないVMの上で実行されます。WASIはこのシステムインターフェイスを規格化します。
RubyのWebAssembly/WASIサポートは、このようなプロジェクトを活用することを狙っています。これにより、Ruby開発者がそのような有望なプラットフォームで動くアプリケーションを書けるようになります。
ユースケース
このサポートは、開発者がCRubyをWebAssembly環境で利用することを促進します。たとえば、TryRuby playgroundのCRubyサポートです。CRubyをウェブブラウザの上で試すことができるようになりました。
技術的な話
現時点のWASIとWebAssemblyには、Fiberや例外やGCを実装するための機能に一部足りないものがあります。CRubyではこのギャップを埋めるために、ユーザランドで実行を制御できるAsyncifyというバイナリ変換技術を使っています。
さらに、WASIの上にVFSを実装しました。これにより、Rubyアプリを単一の.wasmファイルに容易にパッケージ化できます。Rubyアプリの配布が少し簡単になります。
参考文献
実用段階になったYJIT
- YJIT は実験段階ではなくなりました
- 1年以上にわたって本番環境でテストされ、安定して稼働する実績があります。
- YJIT は x86-64 と arm64/aarch64 の CPU アーキテクチャと Linux, MacOS, BSD とその他の UNIX プラットフォームをサポートしました
- このリリースでは Mac の M1/M2, AWS Graviton と Raspberry Pi 4 の ARM64 プロセッサに対応してます。
- YJIT をビルドするためには Rust 1.58.0 以降が必要となります [Feature #18481]
- CRuby を YJIT を有効としてビルドするためには、rustc >= 1.58.0 をインストールした上で
./configure
を実行する必要があります - もし、実行時に何かしらの問題に遭遇した場合は YJIT チームに連絡してください
- CRuby を YJIT を有効としてビルドするためには、rustc >= 1.58.0 をインストールした上で
- Ruby 3.2 の YJIT は Ruby 3.1 よりも速くなりました。またメモリのオーバーヘッドが 1/3 となりました。
- YJIT は yjit-bench にあるように通常の Ruby インタプリタより 41% 速くなりました
- JIT のための物理メモリは遅延して確保するようになりました。 Ruby 3.1 と異なり
--yjit-exec-mem-size
に よって確保された仮想メモリのページは物理メモリのページにJITによって実際に使われるまで 割り当てられなくなったため Ruby プロセスのRSS はより小さくなりました。 - JIT によるメモリ消費が
--yjit-exec-mem-size
に達したときに、全てのコードページを解放するコードGCを導入しました。 RubyVM::YJIT.runtime_stats
は、既存のinline_code_size
とoutlined_code_size
キーに加えて、code_gc_count
,live_page_count
,freed_page_count
とfreed_code_size
を コードGC のメトリクスとして表示します
- リリースビルドから
RubyVM::YJIT.runtime_stats
によって統計の大部分を得られるようになりました- ruby コマンドに
--yjit-stats
を付与することで単純に表示することができます (ただしランタイムのオーバーヘッドは生じます)
- ruby コマンドに
- YJIT へ object shapes による最適化が行われました [Feature #18776]
- 定数を無効化する粒度を細かくすることで、新しい定数を定義する際に無効化するコードの量を少なくしました [Feature #18589]
--yjit-exec-mem-size
のデフォルト値は 64 (MiB) と変更されました.--yjit-call-threshold
のデフォルト値は 30 と変更されました
ReDoSに対するRegexpの改善
正規表現マッチングは予想外に時間がかかることがあることが知られています。もし信頼できない入力に対して非効率な可能性のある正規表現をマッチしていると、Denial of Service攻撃を効率的にできてしまう可能性があります(正規表現DoS、ReDoSなどと言われます)。
Ruby 3.2では、ReDoSを大幅に軽減する2つの改善を導入しました。
Regexpのマッチングアルゴリズムの改善
Ruby 3.2から、Regexpのマッチングアルゴリズム自体がメモ化の最適化によって大幅に改善されました。
# 次のマッチングはRuby 3.1では10秒かかりますが、Ruby 3.2では0.003秒で終わります
/^a*b?a*$/ =~ "a" * 50000 + "x"
このアルゴリズムの改善で、ほとんどの(我々の実験では90%程度の)正規表現が線形時間でマッチ判定できるようになります。
この最適化は、マッチングのたびに入力長に比例したメモリを消費することがあります。このメモリ確保は通常遅延され、また、通常の正規表現であれば入力長のたかだか10倍程度のメモリを消費するだけなので、実用上の問題は発生しないと考えています。もし実アプリの正規表現マッチングでメモリ不足に陥った場合は報告してください
提案チケットは https://bugs.ruby-lang.org/issues/19104 です。
Regexpのタイムアウトの導入
上記の最適化は、ある種の正規表現には適用できません。たとえば、後方参照や先読み・後読みのような発展的機能や、非常に大きい固定回数繰り返しを使っている場合には適用されません。この場合の対策として、正規表現マッチングのタイムアウト機能が導入されました。
Regexp.timeout = 1.0
/^a*b?a*()\1$/ =~ "a" * 50000 + "x"
#=> 1秒後にRegexp::TimeoutError
なお、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
の機能が Ruby に統合されました。syntax_suggest
は、Ruby のコードの実行時に以下の例のようにエラーが起きた場所を容易に発見する機能を提供します。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]
^^^^^^
言語機能
-
匿名の可変長引数と可変長キーワード引数はメソッドパラメータとしてだけではなく、引数としても 使えるようになりました [Feature #18351]
def foo(*) bar(*) end def baz(**) quux(**) end
-
1つの引数と残りをキーワードとして受け取る 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
以下のように評価されます
foo1
foo2
baz1
baz2
-
Find patternが実験的機能ではなくなりました。 [Feature #18585]
-
可変長パラメータ (
*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_vm/mjit/compiler
として Ruby で再実装されました。 - MJIT コンパイラは MJIT ワーカーによって呼ばれた native スレッドの代わりに
fork されたプロセスによって実行されるようになりました。 [Feature #18968]
- そのため、Microsoft Visual Studio (MSWIN) はサポート対象外となりました
- MinGW はサポート対象外となりました [Feature #18824]
--mjit-min-calls
は--mjit-call-threshold
にリネームされました--mjit-max-cache
のデフォルト値は 10000 から 100 に戻されました
PubGrub
-
Bundler 2.4 は利用する依存解決ライブラリを Molinillo から PubGrub に変更しました。
- PubGrub は Dart 言語のパッケージマネージャである
pub
で使われている次世代の依存解決アルゴリズムです。 - この変更により、bundler を実行後に異なるライブラリの依存解決結果となる可能性があります。もし、不具合や気になる点を見つけた方は RubyGems/Bundler issues までご報告ください。
- PubGrub は Dart 言語のパッケージマネージャである
-
RubyGems は Ruby 3.2 では引き続き Molinillo ライブラリを利用しています。今後、RubyGems も PubGrub に変更し、Bundler と同じライブラリを使う予定です。
その他の注目すべき 3.1 からの変更点
- Data
-
単純かつ不変な値オブジェクトを表現するための新たなコアクラス Data が追加されました。 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#shift
はハッシュが空の時には、デフォルト値やデフォルトの proc を呼ぶ代わりに常に nil を返します。 [Bug #16908]
- MatchData
MatchData#byteoffset
が追加されました [Feature #13110]
- Module
Module.used_refinements
が追加されました [Feature #14332]Module#refinements
が追加されました [Feature #12737]Module#const_added
が追加されました [Feature #17881]
- Proc
Proc#dup
はサブクラスのインスタンスを返すようになりました [Bug #17545]Proc#parameters
がlambda
キーワードを受け取ることができるようになりました [Feature #15357]
- Refinement
Refinement#refined_class
が追加されました [Feature #12737]
- RubyVM::AbstractSyntaxTree
parse
,parse_file
,of
へerror_tolerant
オプションが追加されました [Feature #19013] このオプションを指定した場合- SyntaxError が発生しなくなります
- 文法上正しくない入力に対しても抽象構文木を返します
- 入力を最後まで読んだときに
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
Set
はrequire "set"
を実行しなくても使用できる組み込みのクラスとなりました。 [Feature #16989] この機能はSet
を参照した時、またはEnumerable#to_set
を呼んだ時に有効となります。
- String
String#byteindex
とString#byterindex
が追加されました [Feature #13110]- Unicode のバージョンを 15.0.0 へ、絵文字のバージョンを 15.0 へとアップデートしました [Feature #18639] (この変更は正規表現にも反映されます)
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">
-
互換性に関する変更
定数の削除
以下の非推奨定数は削除されました
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]
標準添付ライブラリの互換性に関する変更
3rd パーティライブラリのソースコード同梱廃止
-
libyaml
やlibffi
のような 3rd パーティのライブラリのソースコードの同梱を廃止しました-
Psych に同梱していた
libyaml
のソースコードは削除されました。ユーザーは自身で Ubuntu や Debian プラットフォームならlibyaml-dev
パッケージをインストールする必要があります。このパッケージ名称はプラットフォームごとに異なります。 -
Fiddle に同梱していた
libffi
のソースコードも削除されました
-
-
Psych と fiddle には特定バージョンの
libyaml
やlibffi
のソースコードを静的リンクするための機能が追加されました。libyaml-0.2.5
をリンクしてビルドする場合は以下のように実行します。$ ./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
変数- “taintedness” と “trustedness” 関数 [Feature #16131]
標準添付ライブラリのアップデート
-
Bundler
- gem を Rust で書くための雛形作成コマンドとして
bundle gem --ext=rust
をサポートしました。[GH-rubygems-6149] git clone
をより速く実行できるように改善しました [GH-rubygems-4475]
- gem を Rust で書くための雛形作成コマンドとして
-
RubyGems
- cargo builder を 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"
と記述しなくても動かすことができます - 詳しくは What’s new in Ruby 3.2’s IRB? をみてください。
- これらは Gemfile に
- Pry のようなコマンドや機能が複数追加されました。
edit
とshow_cmds
(Pry のhelp
コマンド相当) が追加されましたls
コマンドに出力をフィルタするための-g
または-G
オプションが追加されましたshow_source
のエイリアスとして$
が追加されました、また引数をクオートする必要がなくなりましたwhereami
のエイリアスとして@
が追加されました
- debug.gem と統合したコマンドが複数追加されました:
-
以下の default gems のバージョンがアップデートされました。
- 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
-
以下の bundled gems のバージョンがアップデートされました。
- 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
default gems と bundled gems の詳細については Logger の GitHub Releases のような GitHub releases または changelog ファイルを参照してください。
その他詳細については、NEWS ファイルまたはコミットログを参照してください。
なお、こうした変更により、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) によって1993年に開発が始められ、今もオープンソースソフトウェアとして開発が続けられています。Rubyは様々なプラットフォームで動き、世界中で、特にWebアプリケーション開発のために使われています。