Ruby 3.1 がリリースされた!

今年も無事にクリスマスに Ruby がリリースされました!

Ruby 3.1 で追加される機能などは以下のスライドで紹介しているので気になる人は読んでみてください。

Ruby 3.1 へ移行するときの注意点

Ruby 3.1 で非互換になった機能があるのでいくつか紹介します。
これ以外にも細かい変更点はあるので詳しくは NEWS などを参照してください。

YAML.load が非互換になる

Ruby 3.1 では YAML の実装である Psych のバージョンが 4.0.0 にメジャーアップデートされます。
この影響により YAML.load など一部の機能が非互換になります。
例えば YAML.loadYAML.safe_load に置き換わったので次のようにエイリアスを使用している YAML ファイルは YAML.load では読み込めなくなります。

require "yaml"

data = <<~EOS
default: &default
  aaa: aaa
development:
  <<: *default
EOS

# Ruby 3.0 => OK 読み込める
# Ruby 3.1 => NG 読み込めない
pp YAML.load(data)

これを回避する方法として YAML.unsafe_load を使用する方法があります。

# OK
# .unsafe_load を使用すると Ruby 3.1 でも読み込めるようになる
pp YAML.unsafe_load(data)

読み込んでいる YAML ファイルによっては動かなくなる可能性があるので注意しましょう。
他にも非互換になった箇所があるので詳しくは以下の記事を参照してください。

Class#descendants が Ruby 3.1 には入らなかった

Ruby 3.1 には Class#descendants という新しいメソッドが入る予定だったんですが直前で Revert されました。
そもそも入ってないので基本的に既存のコードには影響はないのですが Rails 7.0.0 がこの Class#descendants に依存している関係で Rails 7.0.0 と Ruby 3.1 の組み合わせだと動かない可能性があるので注意する必要があります。
この問題は Rails 7.0.1 (仮) で対応される予定です。

Module#prepend の挙動が調整

『既に継承リストに存在しているモジュールを prepend した』ときの挙動が調整されました。
これによって以下のように Ruby 2.7 ~ 3.1 間で全ての挙動が異なるのでちょっとだけ気にしておく必要があります。

module M1; end
module M2; end
class A; include M2; end

M2.prepend M1
A.prepend M1

p A.ancestors
# 2.7 => [M1, A, M2, Object, Kernel, BasicObject]
# 3.0 => [A, M1, M2, Object, Kernel, BasicObject]
# 3.1 => [M1, A, M1, M2, Object, Kernel, BasicObject]

refine 内での include / prepend が非推奨になった

refine 内での include / prepend が非推奨になりました。
-W を付けて Ruby を実行すると警告が出るようになります。

using Module.new {
  module Twice
    def twice
      self + self
    end
  end

  refine String do
    # warning: Refinement#include is deprecated and will be removed in Ruby 3.2
    include Twice
  end

  refine Integer do
    # warning: Refinement#include is deprecated and will be removed in Ruby 3.2
    include Twice
  end
}

pp "homu".twice
pp 42.twice

これの代替として import_methods という機能が新しく追加されたので今後は import_methods を使っていく必要があります。

# 使い方は一緒
using Module.new {
  module Twice
    def twice
      self + self
    end
  end

  refine String do
    # no warning
    import_methods Twice
  end

  refine Integer do
    # no warning
    import_methods Twice
  end
}

pp "homu".twice
pp 42.twice

foo[0], bar.baz = a, b の評価順が変更

多重代入を行った際の評価順が変わりました。
以下のようなコードは

foo[0], bar.baz = a, b

Ruby 3.0 では以下の評価順です。

  1. a が評価される
  2. b が評価される
  3. foo が評価される
  4. foo[]= が評価される( foo[0]= )
  5. bar が評価される
  6. barbaz= が評価される( bar.baz=

Ruby 3.1 では以下のような評価順になります。

  1. foo が評価される
  2. bar が評価される
  3. a が評価される
  4. b が評価される
  5. foo[]= が評価される( foo[0]= )
  6. barbaz= が評価される( bar.baz=