サンプルコードでわかる!Ruby 3.0の主な新機能と変更点 Part 2 - 新機能と変更点の総まとめ
この記事はRuby 3.0 Advent Calendar 2020 21日目の記事です。
また、「この記事が参考になった」という方はぜひサポート(対価の支払い)をお願いします。
2021年1月31日までに発生した対価については全額をRubyアソシエーションに寄付いたします。
(2020.2.9追記)
Rubyアソシエーションに記事の収益として1万円を寄付させてもらいました。
サポートしてくださったみなさんには心より感謝申し上げます。どうもありがとうございました!
Zennに書いた記事の収益をRubyアソシエーションに寄付しました - give IT a try
はじめに
Rubyは毎年12月25日にアップデートされます。
Ruby 3.0については2020年12月25日に3.0.0が正式リリースされました。
この記事ではRuby 3.0で導入される変更点や新機能について、サンプルコード付きでできるだけわかりやすく紹介していきます。
ただし、Ruby 3.0で新たに導入される型チェック機構については、すでに別記事で詳しく解説しています。
RBSについて詳しく知りたい方はこちらをご覧ください。
Rubyで型チェック!動かして理解するRBS入門 〜サンプルコードでわかる!Ruby 3.0の主な新機能と変更点 Part 1〜 - Qiita
本記事の情報源
本記事は以下のような情報源をベースにして、記事を執筆しています。
- Ruby 3.0.0 リリース
- NEWS.md
- リリースノートやNEWSに記載されている各種issueとそれに関連するgit diff
- ruby trunk changes
- これからの Ruby と今の Ruby について - Speaker Deck
- プロと読み解く Ruby 3.0 NEWS - クックパッド開発者ブログ
- その他、ネット上の情報源
動作確認したRubyのバージョン
本記事は以下の環境で実行した結果を記載しています。
$ ruby -v
ruby 3.0.0p0 (2020-12-25 revision 95aff21468) [x86_64-darwin20]
フィードバックお待ちしています
本文の説明内容に間違いや不十分な点があった場合はコメント欄から指摘 or 修正をお願いします🙏
それでは以下が本編です!
Ruby 3.0の概要(というか、個人的な印象)
これまでRubyのバージョンは2.xだったので、Ruby 3.0はメジャーバージョンアップとなります。
Ruby 2.0.0は2013年にリリースされたので(参照)、およそ7年ぶりのメジャーバージョンアップです。
一見、劇的な変化はないようにも見えますが、変更点をじっくり見ていくと例年のアップデートとは若干毛色が違うように感じました。
後方互換性を窓から投げ捨てるような大きな変化はないが、マニアックな仕様変更がちょこちょこある
まず、大前提としてガラッと大きく文法が変わったりすることはありません。素直でシンプルなRubyプログラムであれば、Ruby 3.0でも問題なく動作するはずですし、Ruby 2.x時代に学んだ知識が3.0で急に役に立たなくなる、などということもありません。
とはいえ、後方互換性を捨てる仕様変更点がいつもより多い印象があるのもたしかです。
その最たるものがキーワード引数と通常の引数の分離だと思います。Ruby 2.7時代に"warning: Using the last argument as keyword parameters is deprecated"のような警告が出ていたコードはRuby 3.0ではエラーになります。
def f1(key: 0)
key
end
# Ruby 2.7では警告が出ていたが、3.0ではエラー
f1({key: 42})
#=> ArgumentError (wrong number of arguments (given 1, expected 0))
メンテナンスが活発なgemはすでにキーワード引数関連の対応が終わっていると思いますが、長年メンテされずに放置されているgemはRuby 3.0に上げたタイミングでエラーが発生する恐れがあります。
他にも「後方互換性に影響があるから」という理由で長年対応を見送られていたissueが、「Ruby 3.0が出るから」という理由で対応されたケースもいくつかありました。
個人的な印象では「その変更でインパクトを受けるのは、よっぽどマニアックなコードを書いてた人だけだろう」という仕様変更がほとんどでしたが、自分が書いたコードには影響がなくても、プロジェクト内でrequireしている外部のgemでは「インパクトを受けるマニアックなコード」が使われているかもしれません。(たとえばStringクラスやArrayクラスを継承したクラスを作っている等)
なので、しっかりテストコードを書いておかないと、Ruby 3.0で突然エラーが出たり、おかしな実行結果になったりするリスクが、低い確率ながらもあるんじゃないかなと思いました。
Rubyの未来を担う新機能が導入された
RBS/TypeProfといった型チェックに向けた基盤整備と、RactorやFiber Schedulerといった並行・並列処理の新機能もRuby 3.0の大きなトピックです。
登場したばかりの新機能なので、広く普及したり安定して使えたりするようになるまではもうしばらく時間がかかるかもしれませんが、これらの機能は「Rubyには型がないから」「Rubyは遅いから」といったネガティブな評価を覆す可能性を秘めています。
上記のような変更点にリソースが注ぎ込まれたせいか、例年に比べると日常的なプログラミングでよく使いそうな便利機能の追加は少し控えめな印象も受けます。ですが、Hash#except
のように、Railsでお馴染みのメソッドがRuby本体に逆輸入されたようなケースもあります。
その他の注目ポイント
他にも右代入っぽく使える1行パターンマッチング構文や、endlessメソッド定義構文など、ちょっと攻めた実験的機能が追加されている点も興味深いです。
# 右代入っぽく使える1行パターンマッチング構文(実験的機能)
data = {name: 'Alice', age: 20, zodiac: 'Capricorn'}
data => {name:, zodiac:}
name #=> "Alice"
zodiac #=> "Capricorn"
# endlessメソッド定義構文(実験的機能)
def version_3?(v) = v.to_f >= 3.0
version_3?('2.7') #=> false
version_3?('3.0') #=> true
また、昔からRubyを使っている人はエラー発生時のバックトレースがRuby 2.4以前の表示に戻った点も嬉しかったりするのではないでしょうか。(一方で、最近Rubyを始めた人は最初ちょっと戸惑うかも?)
Ruby 2.7.2以上を使っている場合は「見えない警告」に注意!
Ruby 2.7.2以降では非推奨機能に対する警告出力がデフォルトでOFFになっています。なので、自動テストを実行した際に「1つも警告が出ていないから大丈夫!」と思ってRuby 3.0に上げてしまうと、予期せぬタイミングでエラーが発生するかもしれません。
# Ruby 2.7.2以降では非推奨機能の警告出力がOFFになっているため、警告がないとは言い切れない
$ bundle exec rspec
Randomized with seed 52039
..........
Finished in 9.82 seconds (files took 7.58 seconds to load)
10 examples, 0 failures
そうならないように、RUBYOPT=-W:deprecated
オプション(または環境変数)を付けて自動テストを実行するようにしてください。こうすれば非推奨機能が使われているかどうかを確認できます。(RUBYOPT=-w
でもいいですが、非推奨機能以外の警告も出力されるので少し見づらいかもしれません)
# 警告が出力されるようにRUBYOPTオプション付きでテストを実行する
$ RUBYOPT=-W:deprecated bundle exec rspec
/Users/jnito/.rbenv/versions/2.7.2/lib/ruby/gems/2.7.0/gems/actionpack-6.0.0/lib/action_dispatch/middleware/stack.rb:37: warning: Using the last argument as keyword parameters is deprecated; maybe ** should be added to the call
/Users/jnito/.rbenv/versions/2.7.2/lib/ruby/gems/2.7.0/gems/actionpack-6.0.0/lib/action_dispatch/middleware/static.rb:110: warning: The called method `initialize' is defined here
(以下略)
Ruby 3.0に安心して上げるためには、最低限こうした非推奨機能の警告をなくしておく必要があります。
さて、それでは続けてRuby 3.0の具体的な変更点をひとつずつ見ていきます。
ただし、かなり長いのでスマホよりPCで読むことをオススメします。
参考情報:アウトライン版もあります
見出しだけを集めたアウトライン版もあるので、先にそちらに目を通してから、興味を持った変更点を重点的にチェックする、という読み方もアリだと思います😉
【アウトライン版】サンプルコードでわかる!Ruby 3.0の主な新機能と変更点 - give IT a try
言語上の変更点
キーワード引数とハッシュオブジェクトの自動変換が廃止された(キーワード引数と通常の引数の分離)
Ruby 2.7からキーワード引数とハッシュオブジェクトの自動変換は非推奨になり、警告が出ていました。
Ruby 3.0ではこの自動変換が廃止されたため(キーワード引数と通常の引数の分離)、以下のようなコードはエラーとなります。
def f1(key: 0)
key
end
# Ruby 2.7では警告が出ていたが、3.0ではエラー
f1({key: 42})
#=> ArgumentError (wrong number of arguments (given 1, expected 0))
具体的にどういうケースがエラーになるのかは、Ruby 2.7リリース時に書いた以下の記事を参考にしてみてください(警告が出ていたコードがRuby 3.0でエラーになります)。
サンプルコードでわかる!Ruby 2.7の主な新機能と変更点 Part 2 - キーワード引数に関する仕様変更 - Qiita
Procの引数展開の仕様が少し変わった
Ruby 2.7ではProcの引数が*
だけで展開されるときと、*
と**
で展開されるときとで、引数の扱われ方が異なっていました。
# Ruby 2.7の場合
pr = proc{|*a| a}
pr.call([1])
#=> [[1]]
# **kwがあると、同じ引数を与えても*aの中身が上と異なる
pr = proc{|*a, **kw| a}
pr.call([1])
#=> [1]
Ruby 3.0ではどちらも扱われ方が同じになりました。
# Ruby 3.0ではどちらも同じ中身になる
pr = proc{|*a| a}
pr.call([1])
#=> [[1]]
pr = proc{|*a, **kw| a}
pr.call([1])
#=> [[1]]
なお、引数に渡した配列にハッシュが含まれる場合もRuby 2.7と3.0で振る舞いが変わります。
pr = proc{|*a, **kw| [a, kw]}
# Ruby 2.7の場合(ただし警告が出る)
pr.call([1, {a: 1}])
#=> [[1], {:a=>1}]
# Ruby 3.0の場合
pr.call([1, {a: 1}])
#=> [[[1, {:a=>1}]], {}]
...
引数を使う際に通常の引数も併用できるようになった
Ruby 2.7ではあらゆる引数を受け取って、別のメソッドに引き渡す...引数が導入されました。
def add(a, b)
a + b
end
def add_with_description(...)
# 受け取った引数をすべてそのままaddメソッドに引き渡す
answer = add(...)
"answer is #{answer}"
end
add_with_description(2, 3)
#=> answer is 5
ただし、次のように引数の一部が通常の引数で、残りが...
になっていると構文エラーが発生していました。
# Ruby 2.7
# 引数の一部が通常の引数で、残りが...になっていると構文エラー
def add_with_description(a, ...)
answer = add(a, ...)
"answer is #{answer}"
end
Ruby 3.0では上記のようなケースでもエラーが出なくなりました。
# Ruby 3.0ではこういう記法も有効
def add_with_description(a, ...)
answer = add(a, ...)
"answer is #{answer}"
end
case/in
を使うパターンマッチングが正式に導入された
Ruby 2.7で実験的に導入されたパターンマッチングがRuby 3.0で正式に導入されました。それに伴い、パターンマッチングを使っても警告が出なくなりました。
# Ruby 3.0ではパターンマッチングを使っても警告が出ない
case {status: :error, message: 'User not found.'}
in {status: :success, message: message}
"Success!"
in {status: :error, message: message}
"Error: #{message}"
end
#=> "Error: User not found."
ただし、後述する「1行パターンマッチング」と「findパターン」についてはまだ実験的機能の扱いになっています。
なお、パターンマッチングについてはRuby 2.7リリース時に書いた以下の記事で詳しく説明しています。
in
と=>
の2種類になった(実験的機能)
1行パターンマッチングの構文がRuby 2.7ではin
を使って1行パターンマッチングができました。
# Ruby 2.7
data = {name: 'Alice', age: 20, zodiac: 'Capricorn'}
data in {name:, zodiac:}
name #=> "Alice"
zodiac #=> "Capricorn"
Ruby 3.0ではin
だけでなく、=>
も使えるようになりました。(右代入っぽく使える)
# Ruby 3.0では => も使える
data = {name: 'Alice', age: 20, zodiac: 'Capricorn'}
data => {name:, zodiac:}
name #=> "Alice"
zodiac #=> "Capricorn"
また、in
を使った1行パターンマッチングは、Ruby 3.0ではtrue
/false
を返すようになりました。
data = {name: 'Alice', age: 20, zodiac: 'Capricorn'}
if data in {name:, zodiac:}
# マッチした場合の処理(変数への代入もされる)
name #=> "Alice"
zodiac #=> "Capricorn"
else
# マッチしなかった場合の処理
end
# => を条件分岐で使うと構文エラーになる
if data => {name:, zodiac:}
end
#=> SyntaxError (void value expression)
Ruby 3.0の1行パターンマッチングをまとめると以下のようになります。
# => は右代入するために使う(条件分岐には使えない)
{a: 0, b: 1} => {b:}
# マッチしないと例外が起きる
{a: 0, b: 1} => {c:}
#=> NoMatchingPatternError ({:a=>0, :b=>1})
# in はtrue/falseを返すので条件分岐に使える(右代入としても使うのも可)
{a: 0, b: 1} in {b:} #=> true
# マッチしないとfalseが返る
{a: 0, b: 1} in {c:} #=> false
ただし、1行パターンマッチングはどちらも実験的機能であるため、使用すると警告が出ます。
{a: 0, b: 1} => {b:}
#=> warning: One-line pattern matching is experimental, and the behavior may change in future versions of Ruby!
{a: 0, b: 1} in {b:}
#=> warning: One-line pattern matching is experimental, and the behavior may change in future versions of Ruby!
パターンマッチングにFindパターンが追加された(実験的機能)
Ruby 2.7のパターンマッチでは*
を使って「特定の要素が配列の最初、または最後にあるパターン」を検出することができました。
# "Alice"が最初の要素である配列にマッチ
case ['Alice', 'Bob', 'Carol']
in ['Alice', *others]
p others
end
#=> ["Bob", "Carol"]
# "Alice"が最後の要素である配列にマッチ
case ['Bob', 'Carol', 'Alice']
in [*others, 'Alice']
p others
end
#=> ["Bob", "Carol"]
しかし、「配列内の任意の位置」にある場合はそれをうまく検出する構文がありませんでした。
# Ruby 2.7では次のように*が前後に出てくるパターンマッチは構文エラーとなる
case ['Bob', 'Alice', 'Carol']
in [*others_before, 'Alice', *others_after]
p others_before
p others_after
end
Ruby 3.0では上記のようなパターンマッチが有効になります。(Findパターン)
# Ruby 3.0なら*を前後に使うことができる
case ['Bob', 'Alice', 'Carol']
in [*others_before, 'Alice', *others_after]
p others_before
p others_after
end
#=> ["Bob"]
#=> ["Carol"]
# 前後に任意の要素があってもなくてもマッチする
case ['Alice']
in [*others_before, 'Alice', *others_after]
p others_before
p others_after
end
#=> []
#=> []
ただし、Findパターンは実験的機能であるため、使用すると警告が出ます。
case ['Bob', 'Alice', 'Carol']
in [*others_before, 'Alice', *others_after]
p others_before
p others_after
end
#=> warning: Find pattern is experimental, and the behavior may change in future versions of Ruby!
また、*
が2回使えるのは最初と最後に*
に来る場合のみです。それ以外は構文エラーになります。
# これは2つ目の*が最後に来ていないので構文エラー
case ['Bob', 'Alice', 'Carol', 'Dave']
in [*others_before, 'Alice', *others_after, 'Dave']
p others_before
p others_after
end
#=> syntax error
以下はNEWS.mdに載っているFindパターンの少し複雑な利用方法です。
case ["a", 1, "b", "c", 2, "d", "e", "f", 3]
in [*pre, String => x, String => y, *post]
p pre #=> ["a", 1]
p x #=> "b"
p y #=> "c"
p post #=> [2, "d", "e", "f", 3]
end
endlessメソッド定義構文が導入された(実験的機能)
Ruby 3.0はendを省略して1行でメソッドを定義できる、endlessメソッド定義構文が実験的に導入されました。(実験的機能であるものの、-w
オプションを付けたりしても警告は出ないようです)
# endlessメソッド定義構文を使ってメソッドを定義
def f1 = 42
# 上のメソッド定義は下のメソッド定義と同じ
def f1
42
end
# ()を付けるパターン
def f2() = 42
# 引数を与えるパターン
def f3(x) = x + 1
# 引数ありで()を省略すると構文エラー
def f4 x = x + 1
#=> circular argument reference - x
# syntax error, unexpected end-of-input
# rescue修飾子が付くパターン
def f5(x) = 1 / x rescue nil
メソッド名が=
で終わるセッターメソッドはendlessメソッド定義構文では定義できません。(構文エラー)
# =で終わるメソッドは構文エラー
def age=(n) = @age = n
#=> setter method cannot be defined in an endless method definition
=
で終わらないセッターメソッドなら定義可能です。(Rubyらしいメソッドかどうかは別として)
# =で終わらないセッターメソッドは定義可能
def set_age(n) = @age = n
ちなみに、endlessメソッド定義構文は名前のごとく「end
がないメソッド定義構文」であって、1行で定義することが必須ではありません。たとえば以下のようなメソッド定義も文法的には有効です。(読みやすいかどうかは別として)
def f6(x)
=
x +
1
f6(1)
#=> 2
frozen-string-literal: true
のマジックコメントが有効なとき、式展開された文字列が凍結されなくなった
Ruby 3.0ではfrozen-string-literal: true
のマジックコメントが有効なとき、式展開された文字列が凍結されなくなりました。
# frozen_string_literal: true
"#{123}".frozen?
#=> true (Ruby 2.7)
#=> false (Ruby 3.0)
この背景としては、「同一オブジェクト(object_id
が同じ)ならメモリアロケーションを減らす目的で凍結する意味があるが、式展開した場合は同じ文字列でも同一オブジェクトにならないから凍結する意味がない」というissueがあったためです。
# frozen_string_literal: true
a = "#{123}"
b = "#{123}"
# aもbも"123"で文字列としては同じ
puts a == b
#=> true
# しかし同一オブジェクトではない(この挙動は2.7も3.0も同じ)
puts a.object_id == b.object_id
#=> false
# 同一オブジェクトでないなら、凍結してもメモリアロケーションの節約にならない
# => 3.0では凍結しないことになった
a.frozen? #=> false
b.frozen? #=> false
shareable_constant_value
マジックコメントが導入された(実験的機能)
Ruby 3.0ではshareable_constant_value
というマジックコメントが実験的に導入されました。
このマジックコメントを使うと定数が凍結され、Ractorと連携しやすくなります。
# shareable_constant_value: literal
# リテラルを使って定数に代入すると、オブジェクトが最初から凍結される
X = 'abc'
Y = {foo: []}
X.frozen?
#=> true
Y.frozen?
#=> true
Y[:foo].frozen?
#=> true
このマジックコメントのオプションには上で紹介したliteral
以外にもnone
(デフォルト)、experimental_everything
、experimental_copy
があります。詳しくは公式APIドキュメントを参照してください。
静的型解析の基盤が導入された(RBS、TypeProf)
Ruby 3.0では静的型解析の基盤として、RBSとTypeProfが導入されました。
RBSはRubyの型定義を記述する言語です。Rubyスクリプトとは別のrbs
という拡張子のファイルに型(シグニチャ)を記述します。
class FizzBuzz
def self.run(n)
1.upto(n).map do |n|
if n % 15 == 0
'FizzBuzz'
elsif n % 3 == 0
'Fizz'
elsif n % 5 == 0
'Buzz'
else
n.to_s
end
end
end
end
class FizzBuzz
def self.run : (Integer) -> Array[String]
end
TypeProfを使うと、型推論で自動的にrbsファイル用の型定義を生成できます。
# TypeProfに解析させる実行用のコード
require_relative '../lib/fizz_buzz'
results = FizzBuzz.run(15)
puts results
# TypeProfを使った型推論の出力結果
$ typeprof runner/fizz_buzz_runner.rb
# Classes
class FizzBuzz
def self.run : (Integer) -> Array[String]
end
型のエラーチェックはSteepのような外部のgemを使います。
require_relative '../lib/fizz_buzz'
# わざとrunメソッドの引数を文字列に変える
results = FizzBuzz.run('abc')
puts results
# Steepで型のエラーを検出する
$ steep check
runner/fizz_buzz_runner.rb:3:23: ArgumentTypeMismatch: receiver=singleton(::FizzBuzz), expected=::Integer, actual=::String ('abc')
Ruby 3.0の型解析に関する詳細は、こちらの記事を参照してください。
Rubyで型チェック!動かして理解するRBS入門 〜サンプルコードでわかる!Ruby 3.0の主な新機能と変更点 Part 1〜 - Qiita
非推奨警告がデフォルトで出力されなくなった(Ruby 2.7.2から導入された変更点)
前述の通り、Ruby 2.7.2から非推奨警告がデフォルトで出力されなくなりました。警告を出力する場合は-W:deprecated
オプションを付ける必要があります。-w
を付けると非推奨警告に加えてそれ以外の警告も一緒に出力されます。
# Ruby 3.0では警告が出るコード
b = proc{}
p lambda(&b)
# デフォルトでは警告は出ない
$ ruby warning_example.rb
#<Proc:0x00007f846f84d988 warning_example.rb:1>
# -W:deprecatedオプションや-wオプションを付けると警告が出る
$ ruby -W:deprecated warning_example.rb
warning_example.rb:2: warning: lambda without a literal block is deprecated; use the proc without lambda instead
#<Proc:0x00007f846f84d988 warning_example.rb:1>
次のようにWarning[:deprecated] = true
をセットする方法もあります。
# 非推奨警告を出力するようにする
Warning[:deprecated] = true
b = proc{}
p lambda(&b)
#=> warning: lambda without a literal block is deprecated; use the proc without lambda instead
#=> #<Proc:0x00007fa07c8fa0b0 (irb):1>
$SAFE
と$KCODE
がただのグローバル変数になった
特殊変数の$SAFE
はこれまでRubyの「セーフレベル」を設定するために使われていましたが、Ruby 2.7ではこの変数を利用すると「Ruby 3.0ではただのグローバル変数になる」と警告(予告)が出ていました。
# Ruby 2.7
$SAFE
#=> warning: $SAFE will become a normal global variable in Ruby 3.0
# 0
Ruby 3.0では予告通りただのグローバル変数になりました。
# Ruby 3.0
$SAFE
#=> nil
同様に、かつてはRubyが認識するマルチバイト文字列エンコーディングを格納していた$KCODE
という特殊変数もRuby 3.0からはただのグローバル変数になりました。
# Ruby 2.7(警告あり)
$KCODE
#=> warning: variable $KCODE is no longer effective
#=> nil
# Ruby 3.0(警告なし)
$KCODE
#=> nil
メソッド内で特異クラスを定義する際にyieldを呼ぶことが禁止された
Ruby 3.0ではメソッド内で特異クラスを定義する際にyieldを呼ぶことが禁止され、構文エラーが発生するようになりました(こんなコードを書く人は滅多にいないと思いますが・・・)。
def foo
class << Object.new
yield # ← Ruby 3.0では構文エラー
end
end
#=> SyntaxError (Invalid yield)
ちなみにyieldがなければエラーになりません。
def foo
class << Object.new
puts 'hello.'
end
end
foo
#=> hello.
親クラスと親モジュールで同じクラス変数を書き換えると実行時エラーが発生するようになった
少しややこしいですが、Ruby 3.0では複数の親クラスと親モジュールで同じクラス変数を書き換えると実行時エラーが発生するようになりました。
たとえば以下のような場合です。
class C
@@x = 1
end
module M
@@x = 2
end
class D < C
include M
def foo
@@x
end
end
d = D.new
d.foo
#=> RuntimeError (class variable @@x of M is overtaken by C)
上の例ではクラスDの祖先クラス(ancestors)としてクラスCとモジュールMがあり、CとMが同じ@@x
というクラス変数を書き換えようとしています。
このようなケースで実行時エラーが発生します。
Ruby 2.7では実行時エラーではなく、VERBOSEモード時に警告が出ていました。
# Ruby 2.7では実行時エラーではなく警告が出ていた
$VERBOSE = true
D.new.foo
#=> warning: class variable @@x of M is overtaken by C
#=> 1
なお、クラスとモジュールではなく、親クラスとその親クラスで同じクラス変数を書き換えている場合はエラーや警告は発生しません。
class C
@@x = 1
end
class C2 < C
@@x = 2
end
class D < C2
def foo
@@x
end
end
d = D.new
d.foo
#=> 2
トップレベルでクラス変数を読み書きしようとすると実行時エラーが発生するようになった
Ruby 3.0ではトップレベルでクラス変数を読み書きしようとすると実行時エラーが発生するようになりました。
# Ruby 3.0
@@x = 1
#=> RuntimeError (class variable access from toplevel)
@@x
#=> RuntimeError (class variable access from toplevel)
Ruby 2.7では警告が出ていました。
# Ruby 2.7
@@x = 1
#=> warning: class variable access from toplevel
@@x
#=> warning: class variable access from toplevel
#=> 1
_1
や_2
を使うと構文エラーが発生するようになった
変数やメソッド名にRuby 2.7ではブロックの仮引数として番号指定パラメータ(numbered parameter)が導入されました。
%w(1 20 300).map { _1.rjust(3, '0') }
#=> ["001", "020", "300"]
その代わりに変数に_1
のような名前を付けると警告が出ていました。
# Ruby 2.7では番号指定パラメータと同名の変数を宣言すると警告が出る
_1 = "999"
#=> warning: `_1' is reserved for numbered parameter; consider another name
Ruby 3.0では警告ではなく、構文エラーが発生するようになりました。
# Ruby 3.0では番号指定パラメータと同名の変数を宣言すると構文エラーになる
_1 = "999"
#=> SyntaxError (_1 is reserved for numbered parameter)
他にもメソッド名やブロックの仮引数に_1
のような名前を付けると警告ではなく実行時エラーが出るようになっています。
def _1
end
#=> SyntaxError (_1 is reserved for numbered parameter)
[].map {|_1| }
#=> SyntaxError (_1 is reserved for numbered parameter)
後方互換性が失われる変更点
正規表現リテラルで作られた正規表現オブジェクトが凍結されるようになった
Ruby 3.0では正規表現リテラルで作られた正規表現オブジェクトが凍結されるようになりました。ただし、Regexp.new
など、それ以外の方法で作られた場合は凍結されません。
# 正規表現リテラル(/.../)で作られた場合は凍結される
/\d+/.frozen?
#=> true
# 正規表現リテラル以外で作られた場合は凍結されない
Regexp.new('\d+').frozen?
#=> false
/\d+/.dup.frozen?
#=> false
範囲オブジェクト(Range)が凍結されるようになった
Ruby 3.0では範囲オブジェクト(Range)が凍結されるようになりました。(リテラルを使った場合も使わなかった場合も両方とも)
(1..2).frozen?
#=> true
Range.new(1, 2).frozen?
#=> true
Hash#eachが必ず2要素の配列を渡すようになった
Ruby 3.0.0ではHash#eachが必ず2要素の配列を渡すようになりました。この変更により、->(k, v){ }
のようなラムダをeach
メソッドに渡すと、ArgumentErrorが発生するようになります。
# Hash#eachでは2要素の配列が1つ渡されるので、以下のようなラムダは引数の数が合わない
f = ->(k, v) { }
{a: 1}.each(&f)
#=> ArgumentError (wrong number of arguments (given 1, expected 2))
# 配列を受け取る前提になっていれば正常に動く
f = ->(arr) { p arr }
{a: 1}.each(&f)
#=> [:a, 1]
# もしくは( )を使って配列の各要素を別々に受け取るようになっている場合もOK
f = ->((k, v)) { p "#{k} / #{v}" }
{a: 1}.each(&f)
#=> "a / 1"
ブロックを使う場合は従来通りの書き方で動作します。
{a: 1}.each {|k, v| p "#{k} / #{v}"}
#=> "a / 1"
pipeが閉じられてSTDOUTに書き込みできない場合に、エラーメッセージが出力されなくなった
Ruby 2.7では-n
や-p
といったオプションを付けてRubyを起動した場合に、リダイレクト先のhead
やsed
がpipeを閉じると、エラーメッセージが出力されていました。
# Ruby 2.7
# 出力結果が長大すぎるとheadはpipeを閉じる。するとRubyがエラーを出力する
$ seq 1000000 | ruby -ne 'print if 12..' | head -2
12
13
Traceback (most recent call last):
2: from -e:1:in `<main>'
1: from -e:1:in `print'
-e:1:in `write': Broken pipe @ io_write - <STDOUT> (Errno::EPIPE)
Ruby 3.0ではこのエラーメッセージが出力されなくなりました。
# Ruby 3.0ではエラーメッセージが出ない
$ seq 1000000 | ruby -ne 'print if 12..' | head -2
12
13
定数のTRUE/FALSE/NILが削除された
Ruby 2.7までは定数のTRUE
FALSE
NIL
が定義されていました。(ただし使うと警告が出る)
# Ruby 2.7
Warning[:deprecated] = true
TRUE
#=> warning: constant ::TRUE is deprecated
#=> true
FALSE
#=> warning: constant ::FALSE is deprecated
#=> false
NIL
#=> warning: constant ::NIL is deprecated
#=> nil
Ruby 3.0ではこの定数が削除されたため、使おうとするとNameErrorが発生します。
# Ruby 3.0
TRUE
#=> NameError (uninitialized constant TRUE)
# FALSEとNILも同様
Integer#zero?
がNumeric#zero?
をオーバーライドするようになった
最適化のためRuby 3.0では最適化のためInteger#zero?
がNumeric#zero?
をオーバーライドするようになりました。
このため、もし既存のコードがNumeric#zero?
をオーバーライドしていると、Ruby 3.0ではその変更がInteger#zero?
に反映されなくなります。
class Numeric
# わざとzero?メソッドをオーバーライドする
def zero?
false
end
end
# Ruby 2.7は上のオーバーライドが反映される
puts 0.zero? #=> false
# Ruby 3.0は反映されない
puts 0.zero? #=> true
Enumerable#grep
とEnumerable#grep_v
に正規表現オブジェクトを渡し、ブロックを使わなかった場合、Regexp.last_match
/$~
を変更しなくなった
Ruby 3.0ではEnumerable#grep
とEnumerable#grep_v
に正規表現オブジェクトを渡し、ブロックを使わなかった場合、Regexp.last_match
($~
と同じ)を変更しなくなりました。
# Regexp.last_matchにデータをセットする
'z' =~ /z/
Regexp.last_match #=> #<MatchData "z">
# grepメソッドに正規表現オブジェクトを渡し、ブロックを使わないようにする
["abc", "def"].grep(/b/) #=> ["abc"]
# Ruby 2.7ではnilになるが、3.0では変更されないまま残る
Regexp.last_match #=> #<MatchData "z">
# grep_vを使った場合も同様
["abc", "def"].grep_v(/b/) #=> ["def"]
Regexp.last_match #=> #<MatchData "z">
ただし、ブロックを使った場合は従来通り、Regexp.last_match
の内容も変わります。
# Ruby 2.7 and 3.0
["abc", "def", "cba"].grep(/b./) do |s|
p s
p $~.to_a
end
p $~.to_a
# 実行結果
"abc"
["bc"]
"cba"
["ba"]
["ba"]
open-uri
をrequireしても、openメソッドではURLが開けなくなった
Ruby 2.7までopen-uri
ライブラリをrequireすると、Kernel#open
が拡張され、open
メソッドでURLを開くことができました。
ただし、この機能はセキュリティ上のリスクを招くため、Ruby 2.7では警告が出ていました。
# Ruby 2.7
require 'open-uri'
# openメソッドでURLを開く(ただし警告が出る)
open('https://example.com')
#=> warning: calling URI.open via Kernel#open is deprecated, call URI.open directly or use URI#open
#=> #<StringIO:0x00007f8eed950ca8 @base_uri=...(略)
Ruby 3.0ではこの機能拡張が廃止されたため、URLを渡してもエラーが発生するようになります。
# Ruby 3.0
require 'open-uri'
# openメソッドでURLを開くとエラーが出る
open('https://example.com')
#=> Errno::ENOENT (No such file or directory @ rb_sysopen - https://example.com)
Ruby 3.0ではKernel#open
の代わりにURI.open
を使ってください。
# Ruby 3.0
require 'open-uri'
URI.open('https://example.com')
#=> #<StringIO:0x00007f8eed950ca8 @base_uri=...(略)
バックトレース出力に関する変更点
バックトレースがRuby 2.4以前の表示順に戻った
Ruby 2.5から2.7まで、バックトレースの表示は「下に行くほどエラーの発生場所に近い順番」になっていましたが、Ruby 3.0ではRuby 2.4以前のように「上に行くほどエラーの発生場所に近い順番」に戻りました。
エラーを起こすためのサンプルコード
class A
def hoge
fuga
end
def fuga
1/0
end
end
A.new.hoge
バックトレースの出力例
# Ruby 2.5 - 2.7
$ ruby stacktrace_example.rb
Traceback (most recent call last):
3: from stacktrace_example.rb:10:in `<main>'
2: from stacktrace_example.rb:3:in `hoge'
1: from stacktrace_example.rb:7:in `fuga'
stacktrace_example.rb:7:in `/': divided by 0 (ZeroDivisionError)
# Ruby 3.0(Ruby 2.4以前と同じ)
$ ruby stacktrace_example.rb
stacktrace_example.rb:7:in `/': divided by 0 (ZeroDivisionError)
from stacktrace_example.rb:7:in `fuga'
from stacktrace_example.rb:3:in `hoge'
from stacktrace_example.rb:10:in `<main>'
ただし、irbを使った場合はRuby 2.7時代と同じ表示順になるようです。
# Ruby 3.0
$ irb --prompt simple
?> class A
?> def hoge
?> fuga
?> end
?>
?> def fuga
?> 1/0
?> end
>> end
=> :fuga
>> A.new.hoge
Traceback (most recent call last):
7: from /Users/jnito/.rbenv/versions/3.0.0-rc1/bin/irb:23:in `<main>'
6: from /Users/jnito/.rbenv/versions/3.0.0-rc1/bin/irb:23:in `load'
5: from /Users/jnito/.rbenv/versions/3.0.0-rc1/lib/ruby/gems/3.0.0/gems/irb-1.2.8/exe/irb:11:in `<top (required)>'
4: from (irb):10:in `<main>'
3: from (irb):3:in `hoge'
2: from (irb):7:in `fuga'
1: from (irb):7:in `/'
ZeroDivisionError (divided by 0)
(2020.1.14追記)
irb 1.3.1でirbの表示順もRuby 2.4以前の表示順に戻りました。
gem install irb
で最新のirbをインストールしてください。
コマンドラインオプションの変更点
ヘルプの表示が1画面ずつ表示されるようになった
ruby --help
コマンドを実行してRubyのヘルプを表示した際、これまでは一気に最初から最後まで表示されていましたが、Ruby 3.0からは1画面ずつ表示されるようになりました。
Ruby 2.7の場合(一気に最後までヘルプが表示される)
Ruby 3.0の場合(1画面ぶん表示したらキー入力待ちになる)
ヘルプの表示にはRUBY_PAGER
またはPAGER
環境変数に設定されたプログラムが利用されます。
$ echo $PAGER
less
--backtrace-limit
でエラー発生時に表示されるバックトレースの行数を制限できるようになった
Ruby 3.0では--backtrace-limit
オプションを使って、エラー発生時に表示されるバックトレースの行数を制限できるようになりました。
# バックトレースを制限しない場合
$ ruby test.rb
/Users/jnito/Desktop/test.rb:14:in `f4': undefined local variable or method `f5' for main:Object (NameError)
from /Users/jnito/Desktop/test.rb:10:in `f3'
from /Users/jnito/Desktop/test.rb:6:in `f2'
from /Users/jnito/Desktop/test.rb:2:in `f1'
from /Users/jnito/Desktop/test.rb:17:in `<main>'
# バックトレースを2行に制限する場合
$ ruby --backtrace-limit 2 test.rb
/Users/jnito/Desktop/test.rb:14:in `f4': undefined local variable or method `f5' for main:Object (NameError)
from /Users/jnito/Desktop/test.rb:10:in `f3'
from /Users/jnito/Desktop/test.rb:6:in `f2'
... 3 levels...
コアライブラリの変更点
Array#flatten
などのメソッドが常にArrayクラスのインスタンスを返すようになった
ArrayクラスのサブクラスもRuby 2.7までArrayクラスのサブクラスでflatten
を呼びだしたときは、そのサブクラスのインスタンスが返ってきていました。
class SubArray < Array; end
a = SubArray.new
# Ruby 2.7ではflattenの戻り値は同じサブクラスのインスタンス
a.flatten.class
#=> SubArray
Ruby 3.0ではサブクラスではなく、Arrayクラスのインスタンスが返ってくるようになりました。
# Ruby 3.0ではArrayクラスのインスタンスが返ってくる
a.flatten.class
#=> Array
同様の変更が以下のメソッドにも適用されています。
-
Array#drop
,Array#drop_while
,Array#slice!
,Array#slice / Array#[]
,Array#take
,Array#take_while
,Array#uniq
,Array#*
この変更が行われた背景はArray#rotate
など、同じArrayクラスのメソッドでもサブクラスではなくArrayクラスのインスタンスを返すメソッドがあり、一貫性のなさが問題視されていたからです。
class SubArray < Array; end
a = SubArray.new
# rotateメソッドはRuby 2.7でも3.0でもArrayクラスのインスタンスを返す
a.rotate.class
#=> Array
このissueは2012年に立てられていましたが、「修正はするものの、後方互換性維持のために3.0を出すときまで対応を待つ」という判断が下されていました。
今回8年越しでようやくこのissueがcloseされたわけですが、たしかに後方互換性はなくなるので凝ったコードが書かれたgemなどでは予期せぬエラーが発生したりするかもしれません。
Array#[]
がEnumerator::ArithmeticSequence
を受け取って要素をスライスできるようになった
Ruby 3.0ではArray#[]
がEnumerator::ArithmeticSequence
を受け取って要素をスライスできるようになりました。
以下はNEWS.mdのサンプルコードを一部改変したものです。
dirty_data = ['--', 'data1', '--', 'data2', '--', 'data3']
# (1..).step(2) は [1, 3, 5, 7, ...] のような数列を返す
# これをArray#[]に渡すと、dirty_data[1], dirty_data[3], dirty_data[5]の要素がスライスされる
dirty_data[(1..).step(2)]
#=> ["data1", "data2", "data3"]
Dir.glob
とDir.[]
がデフォルトでソートされた結果を返すようになった
Ruby 2.7までDir.glob
とDir.[]
で返されるファイルの一覧はOSのファイルシステムに依存していて不定でしたが、3.0からは常にソートされて出力されるようになりました。
# Ruby 2.7までは出力順はOSのファイルシステムに依存していたため不定
Dir.glob("*")
#=> ["c.txt", "b.txt", "a.txt"]
# Ruby 3.0からは常にソートされる
Dir.glob("*")
#=> ["a.txt", "b.txt", "b.txt"]
# Dir.[]も同様
Dir["*"]
#=> ["a.txt", "b.txt", "b.txt"]
なお、以前のようにソートは不要という場合は、sort: false
を付けます。
# sort: falseを指定すると以前のようにソートされないまま結果が返る
Dir.glob("*", sort: false)
#=> ["c.txt", "b.txt", "a.txt"]
Dir["*", sort: false]
#=> ["c.txt", "b.txt", "a.txt"]
except
メソッドが追加された
HashとENVに指定されたキー以外の要素を返すRuby 3.0ではHashとENVに指定されたキー以外の要素を返すexcept
メソッドが追加されました。
h = {a: 1, b: 2, c: 3}
h.except(:b)
#=> {a: 1, b: 3}
h.except(:a, :c)
#=> {b: 2}
ENV.except("PATH")
#=> {"RBENV_VERSION"=>"3.0.0-preview2", ...(PATH以外の環境変数が返る)
# ちなみにENVはObjectクラスのインスタンスだが、ENV.exceptの戻り値はHash
ENV.class #=> Object
ENV.except("PATH").class #=> Hash
なお、このメソッドはRails(Active Support)が独自拡張していたメソッドが、Ruby本体にも導入されたものです。
WindowsでENVのキーと値がUTF-8になった
Ruby 2.7までWindows環境ではENVのキーと値がそれぞれOSのロケールに応じてコードページ(日本語なら"Windows-31J"など)が変わっていましたが、Ruby 3.0では"UTF-8"に統一されました。
# 日本語のWindows環境 + Ruby 2.7
ENV["PATH"].encoding #=> Windows-31J
ENV.keys[0].encoding #=> Windows-31J
# 日本語のWindows環境 + Ruby 3.0
ENV["PATH"].encoding #=> UTF-8
ENV.keys[0].encoding #=> UTF-8
このissueも4年前から議論されていましたが、後方互換性が失われるため、3.0まで対応が保留されていました。
Feature #12650: Use UTF-8 encoding for ENV on Windows - Ruby master - Ruby Issue Tracking System
Encoding.default_external
がUTF-8になった
WindowsでRuby 3.0ではWindows環境のEncoding.default_external
がUTF-8になりました。
ただし、RubyInstallerを使っている場合はRuby 2.7からデフォルトでUTF-8になっていたので(参考)、見た目上の変化はありません。
# Windows環境 + Ruby 3.0(またはRubyInstaller + Ruby 2.7)
Encoding.default_external #=> UTF-8
Hash#transform_keys
とHash#transform_keys!
でキーの変換ルールをハッシュで指定できるようになた
Ruby 3.0ではHash#transform_keys
でキーの変換ルールをハッシュで指定できるようになりました。
hash = {created: "2020-12-25", updated: "2020-12-31", author: "foo"}
# Ruby 2.7で:createdを:created_atに、:updatedを:update_timeにそれぞれ変換するコード
hash.transform_keys do |key|
case key
when :created then :created_at
when :updated then :update_time
else key
end
end
#=> {created_at: "2020-12-25", update_time: "2020-12-31", author: "foo"}
# Ruby 3.0では上と同等のコードを次のように書ける
hash.transform_keys(created: :created_at, updated: :update_time)
#=> {created_at: "2020-12-25", update_time: "2020-12-31", author: "foo"}
破壊的メソッド版のHash#transform_keys!
でも同じように使えます。
hash.transform_keys!(created: :created_at, updated: :update_time)
hash
#=> {created_at: "2020-12-25", update_time: "2020-12-31", author: "foo"}
freeze: false
オプション付きでclone
すると、内部的に呼ばれるinitialize_clone
メソッドにもfreeze: false
オプションが渡されるようになった
Ruby 3.0ではfreeze: false
オプション付きでclone
すると、内部的に呼ばれるinitialize_clone
メソッドにもfreeze: false
オプションが渡されるようになりました。
これにより、Setクラスの内部的で保持されるハッシュの凍結状態もこのオプションによって制御できるようになります。
require "set"
# freezeしたSetを作る
frozen_set = Set[].freeze
# freeze: falseでcloneする
cloned = frozen_set.clone(freeze: false)
# cloneされたオブジェクトは凍結されない(Ruby 3.0でも変化なし)
cloned.frozen?
#=> false
# Ruby 3.0では内部的に保持されるハッシュも凍結されない(Ruby 2.7では凍結されていた)
cloned.instance_variable_get(:@hash).frozen?
#=> false
なお、外部のライブラリなどで、独自にinitialize_clone
メソッドをオーバーライドしている場合、freeze:
オプションを受け付けられるようになっていないとArgumentErrorが発生する点に注意が必要です。
freeze: true
オプション付きでclone
すると、clone
で得られたオブジェクトも凍結されるようになった
Ruby 3.0ではfreeze: true
オプション付きでclone
すると、clone
で得られたオブジェクトも凍結されるようになりました。
Ruby 2.7までは凍結されたオブジェクトに対して、freeze: false
を指定すると凍結されていないオブジェクトを返す、というユースケースだけを考慮していました。しかし、これだと仕様がわかりづらいので、freeze: true
が指定された場合も凍結されたオブジェクトが返るようになりました。
また、freeze: false
のときと同様に、内部的に呼ばれるinitialize_clone
メソッドにもfreeze: true
オプションが渡されます。
require "set"
# freezeしないSetを作る
unfrozen_set = Set[]
# freeze: trueでcloneする
cloned = unfrozen_set.clone(freeze: true)
# Ruby 3.0では凍結されている(Ruby 2.7では凍結されていなかった)
cloned.frozen?
#=> true
# Ruby 3.0では内部的に保持されるハッシュも凍結される(Ruby 2.7では凍結されていなかった)
cloned.instance_variable_get(:@hash).frozen?
#=> true
eval
を呼んだ場合、__FILE__
と__LINE__
が、それぞれ"(eval)"の文字列と、評価される文字列内での行番号を返すようになった
Bindingオブジェクト付きでRuby 2.7まではBindingオブジェクト付きでeval
を呼んだ場合、__FILE__
と__LINE__
は、それぞれそのスクリプトが書かれたファイル名と、そのファイル内の行番号を返していました。(加えて警告も出ていた)
puts eval('__FILE__', binding)
puts '-' * 20
puts eval('__LINE__', binding) # このファイル内で3行目なので3が返る
# Ruby 2.7
$ ruby test.rb
test.rb:1: warning: __FILE__ in eval may not return location in binding; use Binding#source_location instead
test.rb:1: warning: in `eval'
test.rb
--------------------
test.rb:3: warning: __LINE__ in eval may not return location in binding; use Binding#source_location instead
test.rb:3: warning: in `eval'
3
Rub 3.0では"(eval)"の文字列と、評価される文字列内での行番号を返すようになりました。(警告もなくなった)
puts eval('__FILE__', binding)
puts '-' * 20
puts eval('__LINE__', binding) # 評価される文字列内でで1行目なので1が返る
# Ruby 3.0
$ ruby test.rb
(eval)
--------------------
1
なお、Bindingオブジェクトを渡さない場合はRuby 2.7も3.0も"(eval)"の文字列と、評価される文字列内での行番号を返します。
puts eval('__FILE__')
puts '-' * 20
puts eval('__LINE__')
# Ruby 2.7も3.0も結果は同じ
$ ruby test.rb
(eval)
--------------------
1
Binding#eval
を呼び出したときの__FILE__
と__LINE__
の出力内容が変わった
引数1個でRuby 3.0では引数1個でBinding#eval
を呼び出したときの__FILE__
と__LINE__
の出力内容が変わりました。
このため、以下のようなコードを実行したときのバックトレースの表示内容がRuby 2.7と3.0で異なります。
begin
binding.eval('raise "oops"')
rescue => e
puts e.backtrace
end
# Ruby 2.7で実行した場合
$ ruby binding_eval_example.rb
binding_eval_example.rb:2:in `<main>'
binding_eval_example.rb:2:in `eval'
binding_eval_example.rb:2:in `<main>'
# Ruby 3.0で実行した場合
$ ruby binding_eval_example.rb
(eval):1:in `<main>'
binding_eval_example.rb:2:in `eval'
binding_eval_example.rb:2:in `<main>'
Ruby 3.0でRuby 2.7と同じ結果を得る場合は以下のように書きます。
begin
binding.eval('raise "oops"', *binding.source_location)
rescue => e
puts e.backtrace
end
lambda
メソッドに渡すと警告が出るようになった
ブロックリテラルを使わずにラムダでないprocオブジェクトをRuby 3.0ではブロックリテラルを使わずにラムダでないprocオブジェクトをlambda
メソッドに渡すと警告が出るようになりました。
# 警告の出力を有効にする(-wオプションを付けて起動してもよい)
Warning[:deprecated] = true
# 警告が出る
b = proc{}
lambda(&b)
#=> warning: lambda without a literal block is deprecated; use the proc without lambda instead
def foo(&b)
lambda(&b)
end
# 警告が出る
foo{}
#=> warning: lambda without a literal block is deprecated; use the proc without lambda instead
# ブロックリテラルを使ったり、ラムダのprocオブジェクトを渡す場合は警告が出ない
c = lambda{}
lambda(&c)
foo(&c)
警告が出る場合は警告文にあるとおり、lambdaからprocに書き換えることが推奨されています。
# lambdaの代わりにprocを使えば警告は出ない
b = proc{}
proc(&b)
def foo(&b)
proc(&b)
end
foo{}
この警告が出るようになったのは、lambdaメソッドを使っているのにラムダではないProcオブジェクトが返るのはセマンティクスが一致しない、という指摘があったためです。
# 以下のようなコードはlambdaメソッドを呼んでも、戻り値がラムダにならない
b = proc{}
lambda(&b).lambda?
#=> false
ブロックリテラルを使わずにprocオブジェクトをlambda
メソッドに渡すコードはRuby 3.1以降でエラーになる予定です。
参考
Feature #15973: Let Kernel#lambda always return a lambda - Ruby master - Ruby Issue Tracking System
モジュールAにモジュールBをあとからinclude/prependした場合も、すでにモジュールAをincludeしているクラスにモジュールBの内容が反映されるようになった
Ruby 3.0ではモジュールAにモジュールBをあとからinclude/prependした場合も、すでにモジュールAをincludeしているクラスにモジュールBの内容が反映されるようになりました。
具体例を以下に示します。
module Speakable
def hello
'Hello'
end
end
# SpeakableモジュールをincludeしたPersonクラスを定義する
class Person
include Speakable
end
# Personに対してhelloメソッドが呼び出せる(ここまではRuby 2.7と変化なし)
person = Person.new
person.hello
#=> Hello
module Shoutable
def shout
"#{hello.upcase}!!!"
end
end
# Shoutableモジュールを定義して、Speakableモジュールにあとからincludeする
Speakable.include Shoutable
# Ruby 3.0はShoutableモジュールのshoutメソッドが呼び出せる
# (Ruby 2.7ではNoMethodErrorが発生する)
person.shout
#=> HELLO!!!
module Whisperable
def hello
msg = super
"#{msg.downcase}..."
end
end
# Whisperableモジュールを定義して、Speakableモジュールにあとからprependする
Speakable.prepend Whisperable
# Ruby 3.0はWhisperableモジュールのhelloメソッドが有効になる
# (Ruby 2.7では"Hello"のまま変わらない)
person.hello
#=> hello...
public
, protected
, private
, public_class_method
, private_class_method
が引数としてメソッド名を列挙した配列を受け取れるようになった
Ruby 3.0ではpublic
, protected
, private
, public_class_method
, private_class_method
が引数としてメソッド名を列挙した配列を受け取れるようになりました。
class Hoge
# Ruby 2.7では配列ではなく別個の引数としてメソッド名を渡す必要がある
private :foo, :bar
end
class Hoge
# Ruby 3.0ではメソッド名が配列に入っていてもOK
private [:foo, :bar]
end
この機能追加は次の項で説明する変更点と一緒に利用することを想定しています。
attr_accessor
, attr_reader
, attr_writer
, attr
が戻り値として定義されたメソッドのシンボルの配列を返すようになった
Ruby 3.0ではattr_accessor
, attr_reader
, attr_writer
, attr
が戻り値として定義されたメソッドのシンボルの配列を返すようになりました。(Ruby 2.7まではnil
)
加えてRuby 3.0ではpublic
, protected
, private
が引数として配列を受け取れるようになったため、たとえば次のようにprivateなゲッター/セッターメソッドを1行で書けるようになりました。
# Ruby 2.7の場合
clsss Foo
# attr_accessorがnilを返す
attr_accessor :foo, :bar
# 上で定義したゲッター/セッターメソッドをprivateメソッドにする
private :foo, :foo=, :bar, :bar=
end
# Ruby 3.0の場合
clsss Foo
# attr_accessorが[:foo, :foo=, :bar, :bar=]を返すため、
# privateなゲッター/セッターメソッドを1行で書ける
private attr_accessor :foo, :bar
end
alias_method
が戻り値として定義されたエイリアスメソッドのシンボルを返すようになった
前述のattr_accessor
と同じように、alias_method
が戻り値として定義されたエイリアスメソッドのシンボルを返すようになりました。(Ruby 2.7まではself
)
これにより、privateなエイリアスメソッドを1行で定義したりできるようになります。
class Someone
def yukihiro_matsumoto
"I'm Matz."
end
# privateなmatzメソッドをエイリアスとして定義
private alias_method :matz, :yukihiro_matsumoto
def to_s
matz
end
end
someone = Someone.new
someone.yukihiro_matsumoto #=> I'm Matz.
someone.to_s #=> I'm Matz.
# matzはprivateメソッドなので外部から呼び出せない
someone.matz
#=> NoMethodError (private method `matz' called for #<Someone:0x00007fbed3075078>)
==
とeql?
メソッドが実装された
ProcクラスにRuby 3.0ではProcクラスに==
とeql?
メソッドが実装されました。同一のブロックから作られたProcであればtrue
が返ります。
def return_proc(&block)
block
end
def return_procs(&block)
proc_1 = return_proc(&block)
proc_2 = return_proc(&block)
[proc_1, proc_2]
end
# 同じブロックから作られたProcオブジェクトならtrue
proc_1, proc_2 = return_procs { }
proc_1 == proc_2 #=> true
proc_1.eql?(proc_2) #=> true
# 同じブロックから作られたProcオブジェクトでなければfalse
other_1, other_2 = return_procs { }
proc_1 == other_1 #=> false
proc_1.eql?(other_1) #=> false
この機能はRSpecの開発チームからのリクエストで追加されました。
並行・並列処理プログラミングをサポートする新しいライブラリ、Ractorが追加された(実験的機能)
Ruby 3.0では並行・並列プログラミングをサポートする新しいライブラリ、Ractorが追加されました。
Ractorを使うとスレッドセーフな並行・並列プログラミングを、わかりやすいAPIで実現できます。
(注:Ractorは2020年3月頃までGuildという名前で呼ばれていました)
以下は時間がかかる何らかの処理(work
)を9回、Ractorを使って並列に実行した場合と、Ractorなしで直列に実行した場合の比較です。
COUNT = 9
# 1秒未満のsleepを入れてその秒数を返すメソッド
# (時間がかかる何らかの処理をシミュレートする)
def work
n = rand
sleep(n)
n
end
# Ractorを使う場合
# http://www.atdot.net/~ko1/activities/2020_ruby3summit.pdf を参考にして実装
def with_ractor
rs = (1..COUNT).map do |i|
Ractor.new(i) do |i|
[i, work]
end
end
until rs.empty?
r, (i, result) = Ractor.select(*rs)
rs.delete r
puts format_result(i, result)
end
end
# 単純にループする場合
def without_ractor
(1..COUNT).map do |i|
result = work
puts format_result(i, result)
end
end
# 実行結果をフォーマットするメソッド
def format_result(i, result)
"no.%d, sleep: %.2f" % [i, result]
end
# 起動時引数が0ならRactorなし、それ以外はRactorありで実行
if ARGV[0] == '0'
without_ractor
else
with_ractor
end
Ractorを使わない場合(直列に実行)
$ ruby test.rb 0
no.1, sleep: 0.20
no.2, sleep: 0.29
no.3, sleep: 0.86
no.4, sleep: 0.30
no.5, sleep: 0.43
no.6, sleep: 0.69
no.7, sleep: 0.82
no.8, sleep: 0.37
no.9, sleep: 0.78
Ractorを使った場合(並列に実行)
※実験的な機能ということでまだ警告が出ます。
$ ruby test.rb 1
<internal:ractor>:38: warning: Ractor is experimental, and the behavior may change in future versions of Ruby! Also there are many implementation issues.
no.6, sleep: 0.03
no.1, sleep: 0.06
no.7, sleep: 0.33
no.5, sleep: 0.51
no.4, sleep: 0.53
no.9, sleep: 0.57
no.8, sleep: 0.60
no.3, sleep: 0.74
no.2, sleep: 0.94
Ractorに関する詳しい説明は以下の資料をご覧ください。
- ruby/ractor.md at master · ruby/ruby (英語)
- RubyKaigi Takeout 2020の笹田さんの登壇資料(英語)
- RubyKaigi Takeout 2020の笹田さんの登壇動画(日本語)
- Ruby3 さみっと onlineの笹田さんの登壇資料(日本語)
Random::DEFAULT
がRandomクラスオブジェクトを返すようになり、なおかつ警告対象となった
Ruby 2.7まで、Random::DEFAULT
はデフォルトの疑似乱数生成器として、Randomクラスのインスタンスを返していました。
# Ruby 2.7
Random::DEFAULT
#=> #<Random:0x00007fd14706ff48>
Ruby 3.0ではRandomクラスそのものが返るようになりました。さらに、この定数を使うと警告が出るようになっています。
Warning[:deprecated] = true
# Ruby 3.0
Random::DEFAULT
#=> warning: constant Random::DEFAULT is deprecated
#=> Random
ただし、rand
メソッドなど、主要なメソッドはクラスメソッドとインスタンスメソッドの両方に定義されているため、ほぼ同じように使えます。
# Ruby 2.7
Random::DEFAULT.rand
#=> 0.1220878188847272
# Ruby 3.0
Random::DEFAULT.rand
#=> 0.8472171439434538
なお、この変更はRactor内でRandom::DEFAULT
を使ってもエラーが出ないようにするための変更だったようです。
String#upcase
などのメソッドが常にStringクラスのインスタンスを返すようになった
StringクラスのサブクラスもRuby 2.7までStringクラスのサブクラスでupcase
を呼びだしたときは、そのサブクラスのインスタンスが返ってきていました。
class SubString < String; end
s = SubString.new
# Ruby 2.7ではupcaseの戻り値は同じサブクラスのインスタンス
s.upcase.class
#=> SubString
Ruby 3.0ではサブクラスではなく、Stringクラスのインスタンスが返ってくるようになりました。
# Ruby 3.0ではStringクラスのインスタンスが返ってくる
s.upcase.class
#=> String
同様の変更が以下のメソッドにも適用されています。
-
String#*
,String#capitalize
,String#center
,String#chomp
,String#chop
,String#delete
,String#delete_prefix
,String#delete_suffix
,String#downcase
,String#dump
,String#each_char
,String#each_grapheme_cluster
,String#each_line
,String#gsub
,String#ljust
,String#lstrip
,String#partition
,String#reverse
,String#rjust
,String#rpartition
,String#rstrip
,String#scrub
,String#slice!
,String#slice / String#[]
,String#split
,String#squeeze
,String#strip
,String#sub
,String#succ / String#next
,String#swapcase
,String#tr
,String#tr_s
このissueも2015年から立てられていましたが、Arrayクラスの変更にあわせてStringクラスも同じような変更が入ることになりました。
Bug #10845: Subclassing String - Ruby master - Ruby Issue Tracking System
Arrayクラスと同様、後方互換性が失われる変更なので一部のコードでは予期せぬエラーが発生するかもしれません。
実際、RailsのActiveSupport::SafeBuffer
クラスで互換性問題があったので、修正されたりしています。
Symbol#to_proc
がラムダのProcを返すようになった
Ruby 3.0ではSymbol#to_proc
がラムダのProcを返すようになりました。
# Ruby 2.7
:to_i.to_proc.lambda?
#=> false
# Ruby 3.0
:to_i.to_proc.lambda?
#=> true
この変更の背景は、Symbol#to_proc
で返されるProcオブジェクトがラムダのように引数の個数を厳密に区別するにもかかわらず、lambda?
がfalse
を返すのが不自然だったためです。
# 以下のコードは7.divmod(2)と同じ(渡せる引数は1つだけ)
prc = :divmod.to_proc
prc.call(7, 2)
#=> [3, 1]
# 7.divmod と同じなのでエラー(引数の扱いが厳密)
prc.call(7)
#=> ArgumentError (wrong number of arguments (given 0, expected 1))
# 7.divmod(2, 0)と同じなのでエラー(引数の扱いが厳密)
prc.call(7, 2, 0)
#=> ArgumentError (wrong number of arguments (given 2, expected 1))
Symbol#name
メソッドが追加された
凍結された文字列を返すRuby 3.0ではシンボルの凍結された文字列表現を返すSymbol#name
メソッドが追加されました。
:foo.name
#=> "foo"
# nameで返される文字列は凍結されている
:foo.name.frozen?
#=> true
# to_sも文字列を返すが、凍結されていない
:foo.to_s
#=> "foo"
:foo.to_s.frozen?
#=> false
name
で返される文字列は凍結されているため、同じシンボル表現であれば同一オブジェクトになります。
# nameで返されるオブジェクトは同じ文字列かつ同一オブジェクト
:foo.name.equal?(:foo.name)
#=> true
# to_sで返されるオブジェクトは同じ文字列でも異なるオブジェクト
:foo.to_s.equal?(:foo.to_s)
#=> false
元々はRuby 2.7でSymbol#to_s
が凍結された文字列が返される予定になっていましたが、インパクトが大きいため変更が取り消されていました。
Ruby 3.0ではto_s
メソッドで凍結された文字列を返す代わりに、name
メソッドが凍結された文字列を返すことになりました。
Warning#warn
がcategory
オプション付きで呼ばれるようになった
RubyではWarningモジュールのwarn
メソッドをオーバーライドすることで、警告発生時の挙動をカスタマイズできます。
Ruby 3.0ではこのメソッドが呼び出される際にcategory
オプションが付くようになりました。これにより、カテゴリに応じて警告発生時の挙動を変更できるようになります。
module Warning
def self.warn(message, category: nil)
# 非推奨の警告だったら、警告ではなく例外を発生させる
raise message if category == :deprecated
super
end
end
# categoryに:deprecatedを指定したので、例外が発生する
warn "これはテストです", category: :deprecated
#=> RuntimeError (これはテストです)
オーバーライドしたメソッドの引数が1つの場合でもエラーは起きません。(Ruby側で引数の個数を見て、category
オプションを渡すかどうか判断するため)
module Warning
# 既存のwarnメソッドは引数の個数が1つだけかもしれない
def self.warn(message)
super "#{message.chomp}!!!\n"
end
end
# categoryを指定しても、Ruby側で引数の数を調節してくれるのでエラーは起きない
warn "これはテストです", category: :deprecated
#=> これはテストです!!!
なお、警告のカテゴリには:deprecated
と:experimental
の2種類がありますが、Ruby 3.0の時点では:deprecated
しか渡されないようです。(参照)
標準ライブラリの主な変更点
DateTimeクラスが非推奨クラスになった
DateTimeクラスは非推奨なクラスとなり、DateTimeクラスではなくTimeクラスを使うよう、公式にアナウンスされました。
参考1
But we consider use of DateTime should be discouraged. - matz (Yukihiro Matsumoto)
https://bugs.ruby-lang.org/issues/15712#note-4
参考2
DateTime は deprecated とされているため、 Timeを使うことを推奨します。
https://docs.ruby-lang.org/ja/latest/class/DateTime.html
OpenStructが遅延初期化されなくなった
Ruby 2.7までOpenStructは遅延初期化されていました。
# Ruby 2.7
require 'ostruct'
# formatを呼び出すと:fooを返すOpenStructを作成する
# ただし、formatはKernel#formatと名前が重複している
os = OpenStruct.new(format: :foo)
# まだ初期化されていないので、Kernel#formatメソッドが返る
os.method(:format)
#=> #<Method: OpenStruct(Kernel)#format(*)>
# sendメソッドを使うとKernel#formatが呼び出される(ここではエラーになる)
os.send(:format)
#=> ArgumentError: too few arguments
# formatメソッドを呼びだしたタイミングでオブジェクト独自のformatメソッドが定義される(遅延初期化)
os.format
#=> :foo
# Kernel#formatではなく、オブジェクト独自のformatメソッドが返る
os.method(:format)
#=> #<Method: #<OpenStruct format=:foo>.format() /Users/jnito/.rbenv/versions/2.7.2/lib/ruby/2.7.0/ostruct.rb:190>
# 初期化済みなのでsendメソッドを使うとオブジェクト独自のformatが呼び出される(エラーにならない)
os.send(:format)
#=> :foo
Ruby 3.0では遅延初期化されなくなりました。そのため、send
メソッドを使っても常にオブジェクト独自のメソッドが呼び出されるようになりました。
# Ruby 3.0
require 'ostruct'
os = OpenStruct.new(format: :foo)
# 最初から初期化済みなので、オブジェクト独自のformatメソッドが返る
os.method(:format)
#=> #<Method: #<OpenStruct format=:foo>.format() /Users/jnito/.rbenv/versions/3.0.0-preview2/lib/ruby/3.0.0/ostruct.rb:214
# 初期化済みなのでsendメソッドを使うとオブジェクト独自のformatが呼び出される(エラーにならない)
os.send(:format)
#=> :foo
# 通常通りformatメソッドを呼びだすこともできる
os.format
#=> :foo
OpenStructのpublicなビルトインメソッドが!付きで呼び出せるようになった
Ruby 3.0ではOpenStructのpublicなビルトインメソッドが!付きで呼び出せるようになりました。これにより、to_s: :foo
のようなハッシュが渡された場合でも、ビルトインメソッドのto_s
が呼び出せるようになります。
require 'ostruct'
# 独自のメソッドとしてto_sを持つOpenStructを作成する
os = OpenStruct.new(to_s: :foo)
# ふつうにto_sを呼び出すと、:fooが返る
os.to_s
#=> :foo
# !で終わるメソッドを呼ぶとビルトインメソッドのto_sが呼び出せる
os.to_s!
#=> "#<OpenStruct to_s=:foo>"
OpenStructのYAMLサポートが改善された
Ruby 3.0ではOpenStructのYAMLサポートが改善されました。
詳細については以下のissueとpull requestを参照してください。
- Feature #8382: Format OpenStruct YAML dump and create getters and setters after load. - Ruby master - Ruby Issue Tracking System
- Improved YAML serialization. · ruby/ostruct@683c3d6
OpenStructはなるべく使わない方がよい、という公式見解が出された
OpenStructは以下のようなデメリットがあるため、なるべく使わない方が良い、という公式見解が出されました。
- 内部的に
method_missing
やdefine_singleton_method
を多用しているため、HashやStructに比べてずっと遅い - ビルトインメソッドと名前が重複した場合、Rubyのバージョンによって実行結果が異なる場合がある
- ビルトインメソッドが容易にオーバーライドされてしまう
詳しい説明はAPIドキュメントを参照してください。
SortedSetクラスがsetライブラリから削除された
Ruby 3.0ではSortedSetクラスがsetライブラリから削除されました。
理由としては標準ライブラリの実装がRBTreeという外部ライブラリに依存していたことと、パフォーマンスが貧弱だったためです。
require "set"
# Ruby 3.0ではSortedSetクラスが削除された
SortedSet
#=> The `SortedSet` class has been extracted from the `set` library.You must use the `sorted_set` gem or other alternatives. (RuntimeError)
エラーメッセージにあるとおり、Ruby 3.0でSortedSetを使う場合は別途sorted_set gemをインストールする必要があります。
Set#join
が実装された
要素を連結して文字列を返すRuby 3.0ではSetの要素を連結して文字列を返すSet#join
が実装されました。
# Ruby 3.0
require "set"
set = Set[1, 2, 3]
set.join('=')
#=> "1=2=3"
Ruby 2.7で同じことをする場合は、配列にしてからjoinする必要がありました。
# Ruby 2.7
require "set"
set = Set[1, 2, 3]
set.to_a.join('=')
#=> "1=2=3"
Set同士の大小を比較する<=>演算子が追加された
Ruby 3.0ではSet同士の大小を比較する<=>演算子が追加されました。
require "set"
# 左辺は右辺のサブセットなので-1
Set[1, 2, 3] <=> Set[1, 2, 3, 4]
#=> -1
# 等しいので0
Set[1, 2, 3] <=> Set[3, 2, 1]
#=> 0
# 左辺は右辺のスーパーセットなので+1
Set[1, 2, 3] <=> Set[2, 3]
#=> 1
Set[1, 2, 3] <=> Set[]
#=> 1
# 両者はサブセット、スーパーセットのどちらの関係にもないのでnil
Set[1, 2, 3] <=> Set[1, 2, 4]
#=> nil
その他
ruby2_keywords
を使った場合の空のハッシュを引数に渡したときの挙動が変わった
Ruby 3.0ではキーワード引数とハッシュオブジェクトが別物として扱われるようになったことに伴い、ruby2_keywords
を付けてもkeyword splatsで渡された空のハッシュは削除されるようになりました。
ruby2_keywords def with_r2k(*args)
args
end
def without_r2k(*args)
args
end
# Ruby 2.7では[{}]
assert_equal [], with_r2k(**{})
#=> []
# Ruby 2.7でも[]
assert_equal [], without_r2k(**{})
#=> []
初期化されていないインスタンス変数にアクセスしても警告が出なくなった
Ruby 2.7までは初期化されていないインスタンス変数にアクセスすると、verboseモードで実行した際に警告が出ていました。
# 初期化されていないインスタンス変数にアクセスするコード例
puts @a
# Ruby 2.7
$ ruby --verbose sample.rb
sample.rb:2: warning: instance variable @a not initialized
Ruby 3.0ではverboseモードでも警告が出なくなります。
# Ruby 3.0では警告が出ない
$ ruby --verbose sample.rb
この警告をなくすことで、verboseモードになっていない場合でもRubyのパフォーマンスが向上するらしく、この点が変更要望の主要な目的だったようです。
マルチスレッド関連/GC関連の変更点(見出しのみ)
Ruby 3.0ではマルチスレッドやGC関連の変更点も多数入っています。
特にFiber Schedulerの導入はRuby 3.0の大きなトピックのひとつになっています。(参考)
ですが、僕自身があまり詳しくない分野のため、ちゃんと調べきれていません。
そのため、ここではNEWS.mdに書かれていた見出しだけをリストアップしておきます。(すいません🙏)
詳細は各トピックのissueをご覧ください。
- ConditionVariable#wait may now invoke the
block
/unblock
scheduler hooks in a non-blocking context. [Feature #16786]
Fiber.new(blocking: true/false) allows you to create non-blocking execution contexts. [Feature #16786] - Fiber#blocking? tells whether the fiber is non-blocking. [Feature #16786]
- Fiber#backtrace & Fiber#backtrace_locations provide per-fiber backtrace. [Feature #16815]
- The limitation of Fiber#transfer is relaxed. [Bug #17221]
- IO#nonblock? now defaults to true. [Feature #16786]
- IO#wait_readable, IO#wait_writable, IO#read, IO#write and other related methods (e.g. IO#puts, IO#gets) may invoke the scheduler hook
#io_wait(io, events, timeout)
in a non-blocking execution context. [Feature #16786] - GC.auto_compact= and GC.auto_compact have been added to control when compaction runs. Setting
auto_compact=
to true will cause compaction to occur during major collections. At the moment, compaction adds significant overhead to major collections, so please test first! [Feature #17176] -
Mutex
is now acquired per-Fiber
instead of per-Thread
. This change should be compatible for essentially all usages and avoids blocking when using a scheduler. [Feature #16792] - Queue#pop, SizedQueue#push and related methods may now invoke the
block
/unblock
scheduler hooks in a non-blocking context. [Feature #16786] - Introduce Fiber.set_scheduler for intercepting blocking operations and Fiber.scheduler for accessing the current scheduler. See rdoc-ref:fiber.md for more details about what operations are supported and how to implement the scheduler hooks. [Feature #16786]
- Fiber.blocking? tells whether the current execution context is blocking. [Feature #16786]
- Thread#join invokes the scheduler hooks
block
/unblock
in a non-blocking execution context. [Feature #16786] - Thread.ignore_deadlock accessor has been added for disabling the default deadlock detection, allowing the use of signal handlers to break deadlock. [Bug #13768]
- Kernel.sleep invokes the scheduler hook
#kernel_sleep(...)
in a non-blocking execution context. [Feature #16786]
Rubyコミッタの笹田さんと遠藤さんが解説されている以下の記事を読むとさらに参考になると思います。
プロと読み解く Ruby 3.0 NEWS - クックパッド開発者ブログ
その他の細かい変更点
- IBM720がエンコーディングに追加された
- BigDecimalが3.0.0にアップデートされ、Ractor互換になった
- Bundlerが2.2.3にアップデートされた
- CGIが0.2.0にアップデートされ、Ractor互換になった
- CSVが3.1.9にアップデートされた
- Dateが3.1.1にアップデートされ、Ractor互換になった
- Digestが3.0.0にアップデートされ、Ractor互換になった
- Etcが1.2.0にアップデートされ、Ractor互換になった
- Fiddleが1.0.5にアップデートされた
- IRBが1.2.6にアップデートされruby 3.0.0dev (2020-12-24T09:58:40Z master 7ca2ca9e32) [x86_64-darwin20]た
- JSONが2.5.0にアップデートされ、Ractor互換になった
- PathnameがRactor互換になった
- Psychが3.3.0にアップデートされ、Ractor互換になった
- Relineが0.1.5にアップデートされた
- RubyGemsが3.2.3にアップデートされた
- Setが1.0.0にアップデートされた
- StringIOが3.0.0にアップデートされ、Ractor互換になった
- StringScannerが3.0.0にアップデートされ、Ractor互換になった
-
TCPSocket.new
に:connect_timeout
オプションが追加された - hostnameの検証をスキップするために
Net::HTTP#verify_hostname=
とNet::HTTP#verify_hostname
が追加された - 第1引数がURIの場合、
Net::HTTP.get
とNet::HTTP.get_response
とNet::HTTP.get_print
がリクエストヘッダをハッシュの第2引数として受け取れるようになった -
Net::SMTP
にSNIサポートが追加された -
Net::SMTP.start
メソッドの引数がキーワード引数としても指定できるようになった -
Net::SMTP
でTLSを利用する際にデフォルトでhostnameのチェックをしなくなった - 以下のライブラリが標準ライブラリからデフォルトgemに変更になった
- English abbrev base64 drb debug erb find net-ftp net-http net-imap net-protocol open-uri optparse pp prettyprint resolv-replace resolv rinda set securerandom shellwords tempfile tmpdir time tsort un weakref
- 以下の拡張ライブラリが標準ライブラリからデフォルトgemに変更になった
- digest io-nonblock io-wait nkf pathname syslog win32ole
- net-telnetとxmlrpcがbundled gemから削除された(メンテナ募集中?)
- SDBMが標準ライブラリから削除された(gemとして引き続き利用可能)
- WEBrickが標準ライブラリから削除された(gemとして引き続き利用可能)
- C APIの変更(NEWS.md参照)
- 実装面の各種改善(NEWS.md参照)
- JITコンパイラの各種最適化(NEWS.md参照)
上記の変更点のいくつかも以下の記事で解説されています。
プロと読み解く Ruby 3.0 NEWS - クックパッド開発者ブログ
まとめ
というわけで、この記事ではRuby 3.0の変更点と新機能をいろいろと紹介してみました。
いやあ、今年も盛りだくさんの内容でしたね。
以前から宣言されていたとおり、2020年のクリスマスにRuby 3.0を届けてくれたMatzさんやコミッタのみなさんに感謝したいと思います。どうもありがとうございました!
みなさんもぜひRuby 3.0の新機能を試してみてください😉
PR: 本記事を読んでもよくわからなかったRuby初心者の方へ
「本文に一通り目を通してみたけど、なんかよくわからない用語がたくさん出てきて、イマイチちゃんと理解できなかった😣」というRuby初心者の方は、拙著「プロを目指す人のためのRuby入門」(通称チェリー本)を読んでみてください。
本書の内容を一通り理解すれば、この記事の内容も問題なく読みこなせるはずです!
ちなみに本書の対象バージョンはRuby 2.4.1ですが、Ruby 2.5以降で発生する記述内容との差異は、それぞれ以下の記事にまとめてあります。なので、多少バージョンが古くても安心して読んでいただけます😊
Discussion