🍒

サンプルコードでわかる!Ruby 3.0の主な新機能と変更点 Part 2 - 新機能と変更点の総まとめ

60 min read

この記事は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.0 リリース

この記事ではRuby 3.0で導入される変更点や新機能について、サンプルコード付きでできるだけわかりやすく紹介していきます。

ただし、Ruby 3.0で新たに導入される型チェック機構については、すでに別記事で詳しく解説しています。
RBSについて詳しく知りたい方はこちらをご覧ください。

Rubyで型チェック!動かして理解するRBS入門 〜サンプルコードでわかる!Ruby 3.0の主な新機能と変更点 Part 1〜 - Qiita

本記事の情報源

本記事は以下のような情報源をベースにして、記事を執筆しています。

動作確認した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リリース時に書いた以下の記事で詳しく説明しています。

1行パターンマッチングの構文がin=>の2種類になった(実験的機能)

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があったためです。

Feature #17104: Do not freeze interpolated strings when using frozen-string-literal - Ruby master - Ruby Issue Tracking System

# 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_everythingexperimental_copyがあります。詳しくは公式APIドキュメントを参照してください。

https://github.com/ruby/ruby/blob/v3_0_0_rc2/doc/syntax/comments.rdoc#label-shareable_constant_value+Directive

静的型解析の基盤が導入された(RBS、TypeProf)

Ruby 3.0では静的型解析の基盤として、RBSとTypeProfが導入されました。

RBSはRubyの型定義を記述する言語です。Rubyスクリプトとは別のrbsという拡張子のファイルに型(シグニチャ)を記述します。

fizz_buzz.rb
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を起動した場合に、リダイレクト先のheadsedが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#grepEnumerable#grep_vに正規表現オブジェクトを渡し、ブロックを使わなかった場合、Regexp.last_match/$~を変更しなくなった

Ruby 3.0ではEnumerable#grepEnumerable#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クラスのサブクラスもArray#flattenなどのメソッドが常に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などでは予期せぬエラーが発生したりするかもしれません。

Bug #6087: How should inherited methods deal with return values of their own subclass? - Ruby master - Ruby Issue Tracking System

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.globDir.[]がデフォルトでソートされた結果を返すようになった

Ruby 2.7までDir.globDir.[]で返されるファイルの一覧は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"]

HashとENVに指定されたキー以外の要素を返すexceptメソッドが追加された

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本体にも導入されたものです。

except (Hash) - APIdock

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

WindowsでEncoding.default_externalがUTF-8になった

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_keysHash#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

Bindingオブジェクト付きでevalを呼んだ場合、__FILE____LINE__が、それぞれ"(eval)"の文字列と、評価される文字列内での行番号を返すようになった

Ruby 2.7まではBindingオブジェクト付きでevalを呼んだ場合、__FILE____LINE__は、それぞれそのスクリプトが書かれたファイル名と、そのファイル内の行番号を返していました。(加えて警告も出ていた)

test.rb
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)"の文字列と、評価される文字列内での行番号を返すようになりました。(警告もなくなった)

test.rb
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)"の文字列と、評価される文字列内での行番号を返します。

test.rb
puts eval('__FILE__')
puts '-' * 20
puts eval('__LINE__')
# Ruby 2.7も3.0も結果は同じ
$ ruby test.rb
(eval)
--------------------
1

引数1個でBinding#evalを呼び出したときの__FILE____LINE__の出力内容が変わった

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

ブロックリテラルを使わずにラムダでないprocオブジェクトをlambdaメソッドに渡すと警告が出るようになった

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>)

Procクラスに==eql?メソッドが実装された

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の開発チームからのリクエストで追加されました。

参考
Feature #14267: Lazy proc allocation introduced in #14045 creates regression - Ruby master - Ruby Issue Tracking System

並行・並列処理プログラミングをサポートする新しいライブラリ、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に関する詳しい説明は以下の資料をご覧ください。

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を使ってもエラーが出ないようにするための変更だったようです。

参考
Feature #17322: Deprecate `Random::DEFAULT` and introduce `Random.default()` method to provide Ractor-supported default random generator - Ruby master - Ruby Issue Tracking System

StringクラスのサブクラスもString#upcaseなどのメソッドが常に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クラスで互換性問題があったので、修正されたりしています。

Let AS::SafeBuffer#[] and * return value be an instance of SafeBuffer in Ruby 3.0 by amatsuda · Pull Request #40663 · rails/rails

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メソッドが凍結された文字列を返すことになりました。

参考
Feature #16150: Add a way to request a frozen string from to_s - Ruby master - Ruby Issue Tracking System

Warning#warncategoryオプション付きで呼ばれるようになった

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を参照してください。

OpenStructはなるべく使わない方がよい、という公式見解が出された

OpenStructは以下のようなデメリットがあるため、なるべく使わない方が良い、という公式見解が出されました。

  • 内部的にmethod_missingdefine_singleton_methodを多用しているため、HashやStructに比べてずっと遅い
  • ビルトインメソッドと名前が重複した場合、Rubyのバージョンによって実行結果が異なる場合がある
  • ビルトインメソッドが容易にオーバーライドされてしまう

詳しい説明はAPIドキュメントを参照してください。

OpenStruct@Caveats

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のパフォーマンスが向上するらしく、この点が変更要望の主要な目的だったようです。

参考
Feature #17055: Allow suppressing uninitialized instance variable and method redefined verbose mode warnings - Ruby master - Ruby Issue Tracking System

マルチスレッド関連/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.getNet::HTTP.get_responseNet::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入門|技術評論社
9784774193977.jpg

ちなみに本書の対象バージョンはRuby 2.4.1ですが、Ruby 2.5以降で発生する記述内容との差異は、それぞれ以下の記事にまとめてあります。なので、多少バージョンが古くても安心して読んでいただけます😊

この記事に贈られたバッジ