62
24

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 1 year has passed since last update.

RubyAdvent Calendar 2021

Day 25

サンプルコードでわかる!Ruby 3.1の主な新機能と変更点

Last updated at Posted at 2021-12-25

はじめに

🎄🎄🎄この記事はRuby Advent Calendar 2021 25日目の記事です🎄🎄🎄

Rubyは毎年12月25日にアップデートされます。
Ruby 3.1については2021年12月25日に3.1.0が正式リリースされました。

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

ただし、しっかり内容を確認する時間が取れなかった機能に関してはNEWS.mdの記載内容を転記するだけに留めています :pray:

本記事の情報源

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

動作確認したRubyのバージョン

本記事は以下の環境で実行した結果を記載しています。

$ ruby -v
ruby 3.1.0p0 (2021-12-25 revision fb4df44d16) [arm64-darwin21]

フィードバックお待ちしています

本文の説明内容に間違いや不十分な点があった場合はコメント欄から指摘 or 修正をお願いします🙏

それでは以下が本編です!

言語仕様の変更

他のメソッドにブロックを渡すだけならメソッド定義時に引数名を付けなくてもよくなった

Ruby 3.1では他のメソッドにブロックを渡すだけならメソッド定義時に引数名を付けなくてもよくなりました(匿名ブロック)。

def foo(&)
  bar(&)
end

def bar(&b)
  b.call
end

foo { puts 'hi' }
#=> hi

Ruby 3.0だと上のコードは構文エラーになります。

lib/sample.rb:1: syntax error, unexpected ')', expecting local variable or method
def foo(&)
lib/sample.rb:2: syntax error, unexpected ')'
  bar(&)

パターンマッチのピン演算子にローカル変数以外の式も渡せるようになった

Ruby 3.1ではパターンマッチのピン演算子にローカル変数以外の式(メソッド呼び出し等)も渡せるようになりました。以下はピン演算子にfind_nameというメソッド呼び出しを渡す例です。

def find_name = 'Alice'

name = 'Alice'
case name
in ^(find_name)
  puts 'found'
end
#=> found

Ruby 3.0だと上のコードは構文エラーになります。

lib/sample.rb:5: syntax error, unexpected (, expecting local variable or method
in ^(find_name)
lib/sample.rb:7: syntax error, unexpected `end', expecting end-of-input

式を渡す場合は()で囲む必要があります。()で囲まない場合はローカル変数であることが期待されるため、エラーになります。

# in ^find_name のように()を省略した場合
lib/sample.rb:5: find_name: no such local variable

パターンマッチのピン演算子にインスタンス変数、クラス変数、グローバル変数が渡せるようになった

Ruby 3.0までピン演算子に渡せる変数はローカル変数だけでしたが、Ruby 3.1ではインスタンス変数(@fooなど)やクラス変数(@@fooなど)、グローバル変数($fooなど)も渡せるようになりました。

@foo = 'Alice'

name = 'Alice'
case name
in ^@foo
  puts 'found'
end

Ruby 3.0だと上のコードは構文エラーになります。

lib/sample.rb:5: syntax error, unexpected instance variable, expecting local variable or method
in ^@foo
lib/sample.rb:7: syntax error, unexpected `end', expecting end-of-input

1行パターンマッチが実験的機能でなくなった

Ruby 3.0で実験的機能として導入された1行パターンマッチが実験的機能でなくなりました。Ruby 3.1では実行しても警告が出ません。

if [1, 2, 3] in [Integer, Integer, Integer]
  puts 'matched'
end
#=> matched

{name: 'Alice'} => {name:}
puts name  #=> Alice

Ruby 3.0だと上のコードは実行可能ですが警告が出ます。

lib/sample.rb:1: warning: One-line pattern matching is experimental, and the behavior may change in future versions of Ruby!
lib/sample.rb:5: warning: One-line pattern matching is experimental, and the behavior may change in future versions of Ruby!
matched
Alice

ちなみに、findパターンはRuby 3.1でもまだ実験的機能のままになっているようです。

# findパターンを使うとRuby 3.1でも警告が出る
case ['Bob', 'Alice', 'Carol']
in [*others_before, 'Alice', *others_after]
  # ...
end
#=> warning: Find pattern is experimental, and the behavior may change in future versions of Ruby!

1行パターンマッチで右辺の括弧が省略できるようになった

Ruby 3.1では1行パターンマッチで右辺の括弧が省略できるようになりました。

[0, 1] => _, x
{y: 2} => y:
p x #=> 1
p y #=> 2

Ruby 3.0では下のように括弧を付けないと構文エラーが起きていました。

# Ruby 3.0
[0, 1] => [_, x]
{y: 2} => {y:}
p x #=> 1
p y #=> 2

多重代入の評価順序が単一代入の評価順序と同じになった

(このセクションはNEWS.mdの内容を意訳します)

Ruby 3.1では多重代入の評価順序が単一代入の評価順序と同じになりました。

単一代入の場合、Rubyは左から右の順に評価します。たとえば下のコードであれば、

foo[0] = bar

次のような順番で評価されます。

  1. foo
  2. bar
  3. foo の結果に対して []= が呼ばれる

Ruby 3.0以前のRubyでは多重代入はこの順序に従っていませんでした。たとえば以下のコードであれば、

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

Ruby 3.0以前のRubyでは次のような順序で評価されていました。

  1. a
  2. b
  3. foo
  4. foo の結果に対して []= が呼ばれる
  5. bar
  6. bar の結果に対して baz= が呼ばれる

Ruby 3.1からはこの順序が単一代入と同様に、左辺が先、右辺が後に評価されるようになります。

  1. foo
  2. bar
  3. a
  4. b
  5. foo の結果に対して []= が呼ばれる
  6. bar の結果に対して baz= が呼ばれる

ハッシュリテラルとキーワード引数で値を省略できるようになった

Ruby 3.1ではハッシュリテラルとキーワード引数で値を省略できるようになりました。

name = 'Alice'
age = 20

# 以下のハッシュリテラルは {name: name, age: age} と書いたのと同じ
h = {name:, age:}
puts h #=> {:name=>"Alice", :age=>20}
text = "ABC\nDEF"
chomp = true

# text.lines(chomp: chomp) と書いたのと同じ
p text.lines(chomp:) #=> ["ABC", "DEF"]

この記法でキー名として使用できるのは定数名、ローカル変数名、メソッド名です。

# メソッド名をキー名として使用する場合
def name = 'Alice'
def age = 20

h = {name:, age:}
puts h #=> {:name=>"Alice", :age=>20}

# 定数名をキー名として使用する場合
FOO = 10
BAR = 20

h = {FOO:, BAR:}
puts h #=> {:FOO=>10, :BAR=>20}

この変更点についてより詳しく知りたい場合は以下の記事をご覧ください。

Ractor

Non main-Ractors can get instance variables (ivars) of classes/modules if ivars refer to shareable objects.
(筆者訳)非メインRactorはクラスやモジュールのインスタンス変数が共有可能オブジェクトを参照していれば、そのインスタンス変数を取得できるようになった。

とのことですが、サンプルコードは用意できなかったので、以下のIssueを参照してください。

エンドレスメソッド定義でコマンド構文が書けるようになった

Ruby 3.1ではエンドレスメソッド定義でputs 'Hello'のようなコマンド構文(引数を丸かっこで囲まないメソッド呼び出し)が書けるようになりました。

def greet = puts 'Hello'

greet #=> Hello

Ruby 3.0だと上のコードは構文エラーになります。

lib/sample.rb:1: syntax error, unexpected string literal, expecting `do' or '{' or '('
def greet = puts 'Hello'

なお、Ruby 3.0の場合、puts('Hello')のように丸かっこを使えば構文として有効です。

# Ruby 3.0の場合は丸かっこが必要
def greet = puts('Hello')

greet #=> Hello

ただし、Ruby 3.1でもprivate def greetのように書く場合は丸かっこがないと構文エラーになります。

# Ruby 3.1でも構文エラー
private def greet = puts 'Hello'
#=> lib/sample.rb:1: syntax error, unexpected string literal, expecting `do' or '{' or '('
#   private def greet = puts 'Hi'

# 丸かっこを使えば有効(Ruby 3.0でも有効)
private def greet = puts('Hello')

コマンドラインオプション

--disable-gems オプションがデバッグ目的でのみ使うように明言された

Ruby 3.1からrubygemsを無効化する --disable-gems オプションがデバッグ目的でのみ使用するように明言されました。オプションの指定時の挙動は同じですが、Ruby 3.1ではruby --helpを開くと"only for debugging"と書かれています。

# Ruby 3.0
Features:
  gems            rubygems (default: enabled)

# Ruby 3.1
Features:
  gems            rubygems (only for debugging, default: enabled)

コアクラス(組み込みライブラリ)のアップデート

Array

共通要素が1件以上あればtrueを返すintersect?メソッドが追加された

Ruby 3.1では二つの配列の積集合の結果(つまり共通要素)が1件以上あればtrueを返すintersect?メソッドが追加されました。

a = [1, 2, 3]
b = [3, 4, 5]

# 3が共通要素なのでtrue
puts a.intersect?(b) #=> true

c = [4, 5, 6]

# 共通要素がないのでfalse
puts a.intersect?(c) #=> false

ちなみにRuby 3.0以前では次のように書く必要がありました。

puts (a & b).any? #=> true
puts (a & c).any? #=> false

(参考情報)SetクラスにはRuby 3.0以前からintersect?メソッドが存在しています。

require 'set'

a = Set[1, 2, 3]
b = Set[3, 4, 5]

puts a.intersect?(b) #=> true

c = Set[4, 5, 6]

puts a.intersect?(c) #=> false

Class

継承関係上の子孫クラスを返すdescendantsメソッドが追加された

descendantsメソッドの導入はRuby 3.1では見送られました(参考)。

Ruby 3.1では継承関係上の子孫クラスを返すdescendantsメソッドが追加されました。このメソッドはレシーバクラスを直接、または間接的に継承しているクラスを配列で返します。ただしレシーバ自身や特異クラスは戻り値に含みません。

# 注:Ruby 3.1ではdescendantsメソッドの導入は見送られました
class Animal; end
class Mammal < Animal; end
class Dog < Mammal; end
Animal.descendants #=> [Mammal, Dog]
Mammal.descendants #=> [Dog]
Dog.descendants    #=> []

継承関係上のサブクラスを返すsubclassesメソッドが追加された

Ruby 3.1では継承関係上のサブクラスを返すsubclassesメソッドが追加されました。このメソッドはレシーバクラスを直接継承しているクラスを配列で返します。ただしレシーバ自身や特異クラスは戻り値に含みません。

class Animal; end
class Mammal < Animal; end
class Dog < Mammal; end
class Cat < Mammal; end
Animal.subclasses #=> [Mammal]
Mammal.subclasses #=> [Cat, Dog]
Dog.subclasses    #=> []
Cat.subclasses    #=> []

Dir

Dir.globにFile::FNM_DOTMATCHオプションを渡したときに..が返らなくなった

(これはNEWS.mdに載っていない、個人的に気づいた変更点です)

Dir.glob("*", File::FNM_DOTMATCH)Dir.glob("**", File::FNM_DOTMATCH)というコードを書いた場合、Ruby 3.0までは...が戻り値に含まれていましたが、Ruby 3.1では..が含まれなくなりました。

# Ruby 3.0 ("**"でも同じ)
Dir.glob("*", File::FNM_DOTMATCH)
#=> [".", "..", ".DS_Store", "foo.txt"]

# Ruby 3.1 ("**"でも同じ)
Dir.glob("*", File::FNM_DOTMATCH)
#=> [".", ".DS_Store", "foo.txt"]

Enumerable, Enumerable::Lazy

compactメソッドが追加された

EnumerableクラスとEnumerable::Lazyクラスにcompactメソッドが追加されました。これにより、配列やハッシュでなくてもEnumerableモジュールをincludeしているオブジェクトであればcompactメソッドが使えるようになります。

以下はEnumerableモジュールをincludeした自作クラスに対してcompactメソッドを呼び出す例です。

class Sample
  include Enumerable

  def each
    yield 3
    yield nil
    yield 7
    yield 9
    yield nil
  end
end

sample = Sample.new
p sample.compact #=> [3, 7, 9]
p sample.lazy.compact.force #=> [3, 7, 9]

Enumerable

tallyメソッドが引数としてハッシュを受け取れるようになった

Ruby 3.1ではtallyメソッドが引数としてハッシュを受け取れるようになりました。

まず、tallyメソッドについて簡単に説明しておきます。tallyメソッドは同一の要素の個数を数え上げるメソッドです。

requests = [:pizza, :ramen, :sushi, :ramen]
ret = requests.tally
puts ret #=> {:pizza=>1, :ramen=>2, :sushi=>1}

Ruby 3.1では引数としてハッシュを渡すことでそのハッシュに結果を格納することができます。

requests = [:pizza, :ramen, :sushi, :ramen]
h = {}
ret = puts requests.tally(h)
# 戻り値だけでなく、引数のハッシュにも同じ結果が格納される
puts ret #=> {:pizza=>1, :ramen=>2, :sushi=>1}
puts h   #=> {:pizza=>1, :ramen=>2, :sushi=>1}

引数にハッシュを渡した場合、tallyの戻り値は引数のハッシュと同一オブジェクトになります。

ret.equal?(h) #=> true

引数のハッシュに同一キーが存在していれば、そこにtallyの結果が加算されます。また、無関係なキーについては値はそのままになります。

requests = [:pizza, :ramen, :sushi, :ramen]
h = {pizza: 10, sushi: 20, foo: :bar}
ret = requests.tally(h)
# pizzaとsushiにはカウントが加算される
# fooは無関係なので値は:barのまま
# ramenはキーと値が新規に追加される
puts ret #=> {:pizza=>11, :sushi=>21, :foo=>:bar, :ramen=>2}
puts h   #=> {:pizza=>11, :sushi=>21, :foo=>:bar, :ramen=>2}

同一のキーはあるが、加算できない値の場合は例外が発生します。

h = {pizza: :none}
ret = requests.tally(h)
#=> lib/sample.rb:17:in `each': wrong argument type Symbol (expected Integer) (TypeError)

each_consとeach_sliceが戻り値としてレシーバを返すようになった

Ruby 3.1ではeach_consメソッドとeach_sliceメソッドが戻り値としてレシーバを返すようになりました。

ret = [1, 2, 3, 4].each_cons(2) { |arr| p arr }
#=> [1, 2]
#=> [2, 3]
#=> [3, 4]
p ret #=> [1, 2, 3, 4] (Ruby 3.0はnil)

ret = [1, 2, 3, 4].each_slice(2) { |arr| p arr }
#=> [1, 2]
#=> [3, 4]
p ret #=> [1, 2, 3, 4] (Ruby 3.0はnil)

ちなみにRuby 3.0以前ではどちらもnilが返ってきていました。

File

File.dirnameが第2引数でスキップするパスの階層数を指定できるようになった

Ruby 3.1ではFile.dirnameメソッドが第2引数でスキップするパスの階層数を指定できるようになりました。

path = "/a/b/c/d.txt"
puts File.dirname(path)    #=> /a/b/c
puts File.dirname(path, 0) #=> /a/b/c/d.txt
puts File.dirname(path, 1) #=> /a/b/c
puts File.dirname(path, 2) #=> /a/b
puts File.dirname(path, 3) #=> /a
puts File.dirname(path, 4) #=> /
puts File.dirname(path, 5) #=> /

# 負の値を指定した場合はエラー
puts File.dirname(path, -1)
#=> lib/sample.rb:9:in `dirname': negative level: -1 (ArgumentError)

GC

以下の2点がNEWS.mdに掲載されています。

  • "GC.measure_total_time = true" enables the measurement of GC. Measurement can introduce overhead. It is enabled by default. GC.measure_total_time returns the current setting. GC.stat[:time] or GC.stat(:time) returns measured time in milli-seconds.
  • GC.total_time returns measured time in nano-seconds.

詳しい内容については下記リンクを参照してください。

Integer

to_int メソッドを呼びだして整数への変換を試みるInteger.try_convertが追加された

Ruby 3.1ではto_intメソッドを呼びだして整数への変換を試みるInteger.try_convertが追加されました。

# Integer.try_convertは引数に対してto_intメソッドを呼び出した結果を返す
p 1.to_int #=> 1
p Integer.try_convert(1) #=> 1
p 1.0.to_int #=> 1
p Integer.try_convert(1.0) #=> 1

# to_intメソッドを実装していない場合はnilが返る
p [].to_int
#=> lib/sample.rb:3:in `<main>': undefined method `to_int' for []:Array (NoMethodError)
p Integer.try_convert([]) #=> nil

# 配列にto_intメソッドを特異メソッドとして追加し、
# Integer.try_convertがto_intメソッドを呼び出していることを確認する
a = []
def a.to_int = 0
p Integer.try_convert(a) #=> 0

Kernel

loadメソッドが第2引数としてモジュールを受け取れるようになった

Ruby 3.1ではloadメソッドが第2引数としてモジュールを受け取れるようになりました。この場合、指定されたファイルは第2引数のモジュールをトップレベルモジュールとしてloadされます。

# lib/target.rb
FOO = 123
def greet = 'Hello'
module Greetable; end

# lib/target.rbはGreetableモジュール内でloadされる(グローバルな名前空間を汚染しない)
load 'lib/target.rb', Greetable

# FOOはGreetable内の定数になる
puts Greetable::FOO #=> 123

# トップレベルにFOOは定義されていない
puts FOO #=> uninitialized constant FOO (NameError)

# BarクラスにGreetableモジュールをincludeする
class Bar
  include Greetable
end

# includeされたGreetable#greetを呼び出す(privateメソッドになっているのでsendを使う)
puts Bar.new.send(:greet) #=> Hello

# トップレベルにgreetは定義されていない
puts greet #=> undefined local variable or method `greet' for main:Object (NameError)

なお、Ruby 3.0以前でも第2引数に真の値を設定することでグローバル名前空間を汚染せずにloadする仕組みがありました。この仕組みはRuby 3.1でも引き続き有効です。

# target.rb
FOO = 123
puts "#{FOO} in target.rb"
# 第2引数に真を渡すとグローバル名前空間を汚染せずにloadできる
load 'lib/target.rb', true
#=> 123 in target.rb

# トップレベルにFOOは定義されていない
puts FOO #=> uninitialized constant FOO (NameError)
# デフォルトの仕様でloadする
load 'lib/target.rb'
#=> 123 in target.rb

# FOOがトップレベルに定義される
puts FOO #=> 123

Marshal

load時にオブジェクトをfreezeできるようになった

Ruby 3.1ではMarshal.loadメソッドにfreeze:オプションが追加されました。このオプションを真にするとload時にオブジェクトをfreezeできます。

source = ["foo", {}, /foo/, 1..2]
payload = Marshal.dump(source)

# load時にオブジェクトをfreezeさせる
objects = Marshal.load(payload, freeze: true)

# 値はload前後でどちらも同じ
p source  #=> ["foo", {}, /foo/, 1..2]
p objects #=> ["foo", {}, /foo/, 1..2]

# 配列も配列の要素もすべてfreezeされる
p objects.frozen? 
#=> true
objects.each do |obj|
  puts "#{obj}=#{obj.frozen?}"
end
#=> foo=true
#=> {}=true
#=> (?-mix:foo)=true
#=> 1..2=true

ただし、クラスやモジュールはfreezeされません。

class Foo; end
module Bar; end

# クラスやモジュールをdump/loadする
source = [Foo, Bar]
payload = Marshal.dump(source)
objects = Marshal.load(payload, freeze: true)

p source  #=> [Foo, Bar]
p objects #=> [Foo, Bar]
p objects.frozen?
#=> true

# クラスとモジュールはfreeze: trueでloadしてもfreezeされない
objects.each do |obj|
  puts "#{obj}=#{obj.frozen?}"
end
#=> Foo=false
#=> Bar=false

MatchData

キャプチャの結果を連番やキャプチャ名で取得できるmatchメソッドが追加された

Ruby 3.1ではキャプチャの結果を連番やキャプチャ名で取得できるmatchメソッドが追加されました。

# mはMatchDataクラスのインスタンス
m = '2021-12-25'.match(/(\d+)-(\d+)-(\d+)/)
puts m.match(0) #=> 2021-12-25
puts m.match(1) #=> 2021
puts m.match(2) #=> 12
puts m.match(3) #=> 25
puts m.match(4) #=> index 4 out of matches (IndexError)

m = '2021-12-25'.match(/(?<year>\d+)-(?<month>\d+)-(?<day>\d+)/)
puts m.match(:year)  #=> 2021
puts m.match(:month) #=> 12
puts m.match(:day)   #=> 25
puts m.match(:foo)   #=> undefined group name reference: foo (IndexError)

このメソッドはMatchData#[]に似ていますが、MatchData#[]とは異なり配列を返すことはありません。

# MatchData#[]もMatchData#matchと同じような使い方ができる
m = '2021-12-25'.match(/(?<year>\d+)-(?<month>\d+)-(?<day>\d+)/)
puts m[0]    #=> 2021-12-25
puts m[1]    #=> 2021
puts m[:day] #=> 25

# MatchData#[]は引数によっては配列を返すときがある
p m[1..3] #=> ["2016", "05", "08"]
p m[2, 2] #=> ["05", "08"]

# MatchData#matchは範囲や第2引数を与えることができない
m.match(1..3)
#=> no implicit conversion of Range into Integer (TypeError)
m.match(2, 2)
#=> wrong number of arguments (given 2, expected 1) (ArgumentError)

キャプチャの結果の文字列長を連番やキャプチャ名で取得できるmatch_lengthメソッドが追加された

Ruby 3.1ではキャプチャの結果の文字列長を連番やキャプチャ名で取得できるmatch_lengthメソッドが追加されました。

m = '2021-12-25'.match(/(\d+)-(\d+)-(\d+)/)
puts m.match_length(0) #=> 10
puts m.match_length(1) #=> 4
puts m.match_length(2) #=> 2
puts m.match_length(3) #=> 2
puts m.match_length(4) #=> index 4 out of matches (IndexError)

m = '2021-12-25'.match(/(?<year>\d+)-(?<month>\d+)-(?<day>\d+)/)
puts m.match_length(:year)  #=> 4
puts m.match_length(:month) #=> 2
puts m.match_length(:day)   #=> 2
puts m.match_length(:foo)   #=> undefined group name reference: foo (IndexError)

Method/UnboundMethod

メソッドの可視性を確認できるpublic?/protected?/private?が追加された

Ruby 3.1ではMethodオブジェクトとUnboundMethodオブジェクトに対してメソッドの可視性を確認できるpublic?/protected?/private?メソッドが追加されました。

class Sample
  def m1; end
  protected def m2; end
  private def m3; end
end

sample = Sample.new

# Methodオブジェクトに対して可視性を問い合わせる
puts sample.method(:m1).public?    #=> true
puts sample.method(:m2).public?    #=> false
puts sample.method(:m3).public?    #=> false
puts sample.method(:m2).protected? #=> true
puts sample.method(:m3).private?   #=> true

# UnboundMethodオブジェクトに対して可視性を問い合わせる
puts Sample.instance_method(:m1).public?    #=> true
puts Sample.instance_method(:m2).protected? #=> true
puts Sample.instance_method(:m3).private?   #=> true

Module

間接的にあるモジュールをすでにprependしていても、同じモジュールが再度prependされるようになった

Ruby 3.1では間接的にあるモジュールをprependしていても、同じモジュールが再度prependされるようになりました。

以下のコード例でRuby 2.7、3.0、3.1の挙動の違いを比較してみます。

module Prependable; end
module SuperPrependable; end
module Includable; end
class Sample
  prepend SuperPrependable
  include Includable
end

# ここまでの挙動は同じ
p Sample.ancestors
#=> Ruby 3.1 [SuperPrependable, Sample, Includable, Object, Kernel, BasicObject]
#=> Ruby 3.0 [SuperPrependable, Sample, Includable, Object, Kernel, BasicObject]
#=> Ruby 2.7 [SuperPrependable, Sample, Includable, Object, Kernel, BasicObject]

# Ruby 3.0からはinclude済みのモジュールにあとからprependするとその変更が反映されるようになった
# (Includableモジュールの手前にPrependableが登場している)
Includable.prepend Prependable
p Sample.ancestors
#=> Ruby 3.1 [SuperPrependable, Sample, Prependable, Includable, Object, Kernel, BasicObject]
#=> Ruby 3.0 [SuperPrependable, Sample, Prependable, Includable, Object, Kernel, BasicObject]
#=> Ruby 2.7 [SuperPrependable, Sample, Includable, Object, Kernel, BasicObject]

# Ruby 3.1では間接的にあるモジュールをすでにprependしていても、同じモジュールが再度prependされるようになった
# (Sampleクラスの手前にPrependableモジュールが登場している)
Sample.prepend Prependable
p Sample.ancestors
#=> Ruby 3.1 [Prependable, SuperPrependable, Sample, Prependable, Includable, Object, Kernel, BasicObject]
#=> Ruby 3.0 [SuperPrependable, Sample, Prependable, Includable, Object, Kernel, BasicObject]
#=> Ruby 2.7 [Prependable, SuperPrependable, Sample, Includable, Object, Kernel, BasicObject]

# 直接prependしているモジュールを再度prependしても何も起きない(変更なし)
Sample.prepend SuperPrependable
p Sample.ancestors
#=> Ruby 3.1 [Prependable, SuperPrependable, Sample, Prependable, Includable, Object, Kernel, BasicObject]
#=> Ruby 3.0 [SuperPrependable, Sample, Prependable, Includable, Object, Kernel, BasicObject]
#=> Ruby 2.7 [Prependable, SuperPrependable, Sample, Includable, Object, Kernel, BasicObject]

private/protected/public/module_functionが引数を返すようになった

Ruby 3.1では private/protected/public/module_functionが引数を返すようになりました。

class Sample
  # 引数がないときはnil
  ret = private
  p ret #=> nil

  # 引数が1つならシンボル
  ret = private def foo; end
  p ret #=> :foo

  # 引数が2つ以上ならシンボルの配列
  def bar; end
  def baz; end
  ret = private :bar, :baz
  p ret #=> [:bar, :baz]
end

ちなみにRuby 3.0以前では上のretはいずれもSampleクラス(つまりself)になります。

Process

以下の変更点がNEWS.mdに記載されています。

Process._fork is added. This is a core method for fork(2). Do not call this method directly; it is called by existing fork methods: Kernel.#fork, Process.fork, and IO.popen("-"). Application monitoring libraries can overwrite this method to hook fork events.

詳しい内容については下記リンクを参照してください。

Struct

keyword_initオプションを明示的に指定せずに生成されたStructに対してキーワード引数だけでnewしようとすると警告が出るようになった

Ruby 3.1では keyword_init オプションを明示的に指定せずに生成されたStructに対してキーワード引数だけでnewしようとすると警告が出るようになりました。具体的には以下のようなケースです。

Post = Struct.new(:id, :name)

p Post.new(id: 1, name: "hello")
#=> lib/sample.rb:3: warning: Passing only keyword arguments to Struct#initialize will behave differently from Ruby 3.2. Please use a Hash literal like .new({k: v}) instead of .new(k: v).
#=> #<struct Post id={:id=>1, :name=>"hello"}, name=nil>

ただし、次のようなケースは警告が出ません(書き方によってPost.newで生成されたオブジェクトの内容が異なる点に注意してください)。

Post = Struct.new(:id, :name)

# キーワード引数ではなく、ふつうに2つの引数を渡す場合
p Post.new(1, "hello")
#=> #<struct Post id={:id=>1, :name=>"hello"}, name=nil>
Post = Struct.new(:id, :name)

# キーワード引数ではなく、引数を{}で囲んで明示的にハッシュを渡す場合
p Post.new({id: 1, name: "hello"})
#=> #<struct Post id={:id=>1, :name=>"hello"}, name=nil>
Post = Struct.new(:id, :name, keyword_init: true)

# Struct.newで明示的にkeyword_init: trueを指定した場合
# (キーワード引数を指定したことになる)
p Post.new(id: 1, name: "hello")
#=> #<struct Post id=1, name="hello">
Post = Struct.new(:id, :name, keyword_init: false)

# Struct.newで明示的にkeyword_init: falseを指定した場合
# (1個のハッシュを引数として渡したことになる)
p Post.new(id: 1, name: "hello")
#=> #<struct Post id={:id=>1, :name=>"hello"}, name=nil>
Post = Struct.new(:id, :name)

# 通常の引数とキーワード引数が混在している場合
# キーワード引数は暗黙的にハッシュに変換される
p Post.new(10, id: 1, name: "hello")
#=> #<struct Post id=10, name={:id=>1, :name=>"hello"}>

ちなみに、この警告はRuby 3.2以降でkeyword_init: trueをデフォルトにするための移行措置として発せられるようです。

Post = Struct.new(:id, :name)

# Ruby 3.1の場合(警告あり)
p Post.new(id: 1, name: "hello")
#=> #<struct Post id={:id=>1, :name=>"hello"}, name=nil>

# Ruby 3.2の場合(keyword_init: trueがデフォルトになる予定)
p Post.new(id: 1, name: "hello")
#=> #<struct Post id=1, name="hello">

keyword_initオプションの値を確認できるkeyword_init?メソッドが追加された

Ruby 3.1ではStruct.newしたときのkeyword_initオプションの値を確認できるkeyword_init?メソッドが追加されました。

Foo = Struct.new(:id, :name, keyword_init: true)
p Foo.keyword_init? #=> true

Bar = Struct.new(:id, :name)
p Bar.keyword_init? #=> nil

Baz = Struct.new(:id, :name, keyword_init: false)
p Baz.keyword_init? #=> false

String

unpackとunpack1でoffsetを指定できるようになった

Ruby 3.1では unpackメソッドとunpack1メソッドでoffsetを指定できるようになりました。

p "ABC".unpack("C*")             #=> [65, 66, 67]
p "ABC".unpack("C*", offset: 0)  #=> [65, 66, 67]
p "ABC".unpack("C*", offset: 1)  #=> [66, 67]
p "ABC".unpack("C*", offset: 2)  #=> [67]
p "ABC".unpack("C*", offset: 3)  #=> []
p "ABC".unpack("C*", offset: -1) #=> offset can't be negative (ArgumentError)

p "ABC".unpack1("C*")             #=> 65
p "ABC".unpack1("C*", offset: 0)  #=> 65
p "ABC".unpack1("C*", offset: 1)  #=> 66
p "ABC".unpack1("C*", offset: 2)  #=> 67
p "ABC".unpack1("C*", offset: 3)  #=> nil
p "ABC".unpack1("C*", offset: -1) #=> offset can't be negative (ArgumentError)

Unicode 13.0.0とEmoji 13.0にアップデート

Ruby 3.1のStringはUnicode 13.0.0とEmoji 13.0にアップデートされました。

Thread

ネイティブスレッドのIDを取得できるnative_thread_idメソッドが追加された

Ruby 3.1ではRubyのスレッドが使用しているネイティブスレッドのIDを取得できるnative_thread_idメソッドが追加されました。

p Thread.main.native_thread_id
#=> 6380411

Thread::Backtrace

--backtrace-limit オプションの値を取得できる Thread::Backtrace.limit が追加された

Ruby 3.1では--backtrace-limitオプションの値を取得できるThread::Backtrace.limitメソッドが追加されました。

# lib/sample.rb
p Thread::Backtrace.limit
$ ruby --backtrace-limit=10 lib/sample.rb
10

$ ruby lib/sample.rb
-1 

ちなみに--backtrace-limitオプションはRuby 3.0で追加されたコマンドラインオプションで、エラー発生時に表示されるバックトレースの行数を制限するものです。

# バックトレースを制限しない場合
$ 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...

Thread::Queue

Ruby 3.1ではQueue#initializeが初期値としてEnumerableオブジェクトを受け取れるようになりました。

q = Queue.new([1, 2, 3])
p q.pop    #=> 1
p q.pop    #=> 2
p q.pop    #=> 3
p q.empty? #=> true

上の例では単純に配列を引数として渡していますが、実際は並列処理で使うworkerオブジェクトを複数渡すユースケースを想定しているようです。

Time

Time.newがタイムゾーンをキーワード引数で指定できるようになった

Ruby 3.1ではTime.newin:というキーワード引数でタイムゾーンを指定できるようになりました。

p Time.new(2021, 12, 25, in: "+09:00") #=> 2021-12-25 00:00:00 +0900

Ruby 3.0以前ではタイムゾーンは第7引数だったため、タイムゾーンを指定するためには必ず7個の引数を渡す必要がありました。

# Ruby 3.0以前ではタイムゾーンは7番目の引数として渡す以外なかった
p Time.new(2021, 12, 25, 0, 0, 0, "+09:00") #=> 2021-12-25 00:00:00 +0900

ちなみに、Time.nowTime.atは以前からin:でタイムゾーンを指定することができたため、これに合わせた変更になります。

Time.now(in: "+0800")
#=> 2021-12-20 19:29:37.710056 +0800

Time.at(1582721899, in: "+07:00")
#=> 2020-02-26 19:58:19 +0700

もう一点、Ruby 3.0までは Time.new(2021, 12, 25, "+07:00") のように時間を指定する引数(第4〜6引数)に明らかに数字でない文字列を渡してもエラーなく動作していましたが、Ruby 3.1では整数に変換可能かより厳密に検査されます。

# Ruby 3.0以前: "+07:00"が7時だと見なされる(内部的に"+07:00".to_iを呼んでいる?)
Time.new(2021, 12, 25, "+07:00")
#=> 2021-12-25 07:00:00 +0900

# Ruby 3.1: "+07:00"はエラー
Time.new(2021, 12, 25, "+07:00")
#=> invalid value for Integer(): "+07:00" (ArgumentError)

# "7"であれば文字列でもOK(Ruby 3.0でも3.1でも)
Time.new(2021, 12, 25, "7")
#=> 2021-12-25 07:00:00 +0900

strftimeが不明な地方時を表す"-00:00"をサポートした

Ruby 3.1ではstrftimeメソッドが不明な地方時を表す"-00:00"(下記の引用を参照)をサポートしました。

UTC での時刻はわかるものの地方時が不明なことを示す特別な時差表記 -00:00 があります。

RFC 3339の日付形式

この表記を使うためには"z"にマイナスのフラグ("-")を付けます。

time = Time.at(1640000200).gmtime

# 通常の地方時表示
p time.strftime("%Y-%m-%dT%H:%M:%S%z")    #=> 2021-12-20T11:36:40+0000

# マイナスフラグ(%-zなど)を使って不明な地方時を表す
p time.strftime("%Y-%m-%dT%H:%M:%S%-z")   #=> 2021-12-20T11:36:40-0000
p time.strftime("%Y-%m-%dT%H:%M:%S%-:z")  #=> 2021-12-20T11:36:40-00:00
p time.strftime("%Y-%m-%dT%H:%M:%S%-::z") #=> 2021-12-20T11:36:40-00:00:00

TracePoint

TracePoint.allow_reentry is added to allow reenter while TracePoint callback.
(筆者訳)TracePointコールバックの最中に再度エントリーする(再度入り込む)TracePoint.allow_reentryが追加された。

とのことですが、サンプルコードは用意できなかったので、以下のIssueを参照してください。

$LOAD_PATH

$LOAD_PATH.resolve_feature_pathにloadできない値を渡しても例外が発生しなくなった

Ruby 3.1では $LOAD_PATH.resolve_feature_path にloadできない値を渡しても例外が発生しなくなりました(nilが返ります)。

$LOAD_PATH.resolve_feature_path 'date'
#=> [:rb, "/Users/jnito/.rbenv/versions/3.1.0-dev/lib/ruby/3.1.0/date.rb"]

# loadできない値を渡すとnilが返る
$LOAD_PATH.resolve_feature_path 'dato'
#=> nil

# Ruby 3.0以前では例外が発生していた
$LOAD_PATH.resolve_feature_path 'dato'
#=> cannot load such file -- dato (LoadError)

Fiber Scheduler

以下の6点がNEWS.mdに掲載されています。

  • Add support for Addrinfo.getaddrinfo using address_resolve hook. [Feature #17370]
  • Introduce non-blocking Timeout.timeout using timeout_after hook. [Feature #17470]
  • Introduce new scheduler hooks io_read and io_write along with a low level IO::Buffer for zero-copy read/write. [Feature #18020]
  • IO hooks io_wait, io_read, io_write, receive the original IO object where possible. [Bug #18003]
  • Make Monitor fiber-safe. [Bug #17827]
  • Replace copy coroutine with pthread implementation. [Feature #18015]

詳しい内容については上記の各リンクを参照してください。

Refinement

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

Ruby 3.1ではrefine内でinclude/prependが非推奨になりました。たとえば以下のコードを-wオプション付きで実行すると警告が発生します(Ruby 3.2で削除されるようです)。

module Shufflable
  def shuffle
    chars.shuffle.join
  end
end

module StringShuffle
  refine String do
    include Shufflable
  end
end

using StringShuffle
puts 'hello'.shuffle
$ ruby -w lib/sample.rb
lib/sample.rb:10: warning: Refinement#include is deprecated and will be removed in Ruby 3.2
leloh

この警告が出る場合は以下のようにincludeimport_methodsに置き換えてください。

module StringShuffle
  refine String do
    import_methods Shufflable
  end
end

より詳しい内容はBug #17429や以下のスライドの55〜59ページを参照してください。

標準ライブラリ

default gemがアップデートされた

Ruby 3.1では以下のdefault gemが更新されました。

更新されたdefault gemを表示する
  • RubyGems 3.3.3
  • base64 0.1.1
  • benchmark 0.2.0
  • bigdecimal 3.1.1
  • bundler 2.3.3
  • cgi 0.3.1
  • csv 3.2.2
  • date 3.2.2
  • did_you_mean 1.6.1
  • digest 3.1.0
  • drb 2.1.0
  • erb 2.2.3
  • error_highlight 0.3.0
  • etc 1.3.0
  • fcntl 1.0.1
  • fiddle 1.1.0
  • fileutils 1.6.0
  • find 0.1.1
  • io-console 0.5.10
  • io-wait 0.2.1
  • ipaddr 1.2.3
  • irb 1.4.1
  • json 2.6.1
  • logger 1.5.0
  • net-http 0.2.0
  • net-protocol 0.1.2
  • nkf 0.1.1
  • open-uri 0.2.0
  • openssl 3.0.0
  • optparse 0.2.0
  • ostruct 0.5.2
  • pathname 0.2.0
  • pp 0.3.0
  • prettyprint 0.1.1
  • psych 4.0.3
  • racc 1.6.0
  • rdoc 6.4.0
  • readline 0.0.3
  • readline-ext 0.1.4
  • reline 0.3.0
  • resolv 0.2.1
  • rinda 0.1.1
  • ruby2_keywords 0.0.5
  • securerandom 0.1.1
  • set 1.0.2
  • stringio 3.0.1
  • strscan 3.0.1
  • tempfile 0.1.2
  • time 0.2.0
  • timeout 0.2.0
  • tmpdir 0.1.2
  • un 0.2.0
  • uri 0.11.0
  • yaml 0.2.0
  • zlib 2.1.1

bundled gemがアップデートされた

Ruby 3.1では以下のbundled gemが更新されました。

  • minitest 5.15.0
  • power_assert 2.0.1
  • rake 13.0.6
  • test-unit 3.5.3
  • rexml 3.2.5
  • rbs 2.0.0
  • typeprof 0.21.1

一部のdefault gemがbundled gemに変わった

Ruby 3.1では以下のdefault gemがbundled gemに変わりました。Bundlerを使っている場合は明示的にこれらのgemをGemfileに追加する必要があります。

  • net-ftp 0.1.3
  • net-imap 0.2.2
  • net-pop 0.1.1
  • net-smtp 0.3.1
  • matrix 0.4.2
  • prime 0.1.2
  • debug 1.4.0

カバレッジ測定を一時停止できるようになった

Ruby 3.1ではCoverage.suspendCoverage.resumeを使ってカバレッジ測定を一時停止・再開できるようになりました。以下はFeature #18176に載っていたサンプルコードの抜粋です。

# target.rb
def foo
  :foo
end

def bar
  :bar
end

def baz
  :baz
end
require "coverage"

# Similar to Coverage.start, but does not start the measurement itself
Coverage.setup(oneshot_lines: true)

load "target.rb"

foo              # This call is not counted
Coverage.resume  # Start the measurement
bar              # This call is counted
Coverage.suspend # Stop the measure
baz              # This call is not counted

# The result is only for Line 7, the body of method "bar"
p Coverage.result #=> {"target.rb"=>{:oneshot_lines=>[7]}}

Random::Formatterモジュールがrandom/formatterライブラリに移動した

Ruby 3.1ではRandom::Formatterモジュールがsecurerandomライブラリからrandom/formatterライブラリに移動しました。そのため、random/formatterライブラリをrequireすればRandomオブジェクトに対して以下のメソッドを呼び出すことができます。

  • hex
  • random_bytes
  • base64
  • urlsafe_base64
  • uuid
  • alphanumeric
require 'random/formatter'

p Random.hex
#=> "cc09766ba830589232f1165e962c179f"
p Random.random_bytes
#=> "\xC1m\xC6\xA2^0\xAE{\xD2Xn\xB0;\xFA.\xCF"
p Random.base64
#=> "oMUoHXK/2dTD2lWlFK6IOA=="
p Random.urlsafe_base64
#=> "shWfRtgto5c0ooekLmkOgA"
p Random.uuid
#=> "1d682308-f5ab-4630-9c8b-632247cdbc49"
p Random.alphanumeric
#=> "xmVcnn47AfK4FPdw"

Ruby 3.0以前と同様にSecureRandomのクラスメソッドとして呼び出すことも可能です。

# 以下のコードはRuby 3.0でも3.1でも有効
require 'securerandom'

p SecureRandom.hex
p SecureRandom.random_bytes
p SecureRandom.base64
p SecureRandom.urlsafe_base64
p SecureRandom.uuid
p SecureRandom.alphanumeric

標準ライブラリの後方互換性問題

ERB#initialize の第2引数以降が常に警告を出すようになった

ERB#initializenewメソッド)のシグニチャは以下のようになっています。(公式リファレンスから抜粋)

new(str, safe_level=NOT_GIVEN, trim_mode=NOT_GIVEN, eoutvar=NOT_GIVEN, trim_mode: nil, eoutvar: '_erbout') -> ERB

Ruby 3.1ではERB#initializeに2つ以上の引数を渡すと-wオプション無しでも警告が出るようになりました(Ruby 3.0では-wを付けないと警告が出なかった)。

require 'erb'

ERB.new("Hello.", 0)
#=> warning: Passing safe_level with the 2nd argument of ERB.new is deprecated. Do not use it, and specify other arguments as keyword arguments.

警告メッセージにもあるとおり、第2引数のsafe_level(セーフレベル)はすでに廃止された仕組みなので使うことはできません。

第3引数のtrim_modeや第4引数のeoutvarを使いたい場合は、同名のキーワード引数を使うようにしてください。

ppメソッドがターミナルの幅を考慮するようになった

Ruby 3.1ではppメソッドがターミナルの表示幅を考慮して出力フォーマットを切り替えるようになりました。以下はirb上でppメソッドを呼びだしたときの実行結果です。

# 画面幅が狭いとき
irb(main):001:0> pp Encoding.aliases.take(5)
[["BINARY", "ASCII-8BIT"],
 ["CP437", "IBM437"],
 ["CP720", "IBM720"],
 ["CP737", "IBM737"],
 ["CP775", "IBM775"]]

# 画面幅が広いとき
irb(main):002:0> pp Encoding.aliases.take(5)
[["BINARY", "ASCII-8BIT"], ["CP437", "IBM437"], ["CP720", "IBM720"], ["CP737", "IBM737"], ["CP775", "IBM775"]]

Ruby 3.0では画面幅が広くても上の「狭いとき」と同じ形式で出力されます。

# Ruby 3.0では画面幅が広くても狭くても出力形式が同じ
irb(main):002:0> pp Encoding.aliases.take(5)
[["BINARY", "ASCII-8BIT"],
 ["CP437", "IBM437"],
 ["CP720", "IBM720"],
 ["CP737", "IBM737"],
 ["CP775", "IBM775"]]

Psych.loadがデフォルトでPsych.safe_loadと同等の挙動になった

Ruby 3.1ではPsychのバージョンが3系から4系に上がりました。Psych 4.0ではPsych.loadがデフォルトでPsych.safe_loadと同等の挙動になりました。

4系でエラーが出る場合はPsych 3.3.2をインストールし、Psych.loadPsych.unsafe_loadに置き換えると、いったん3系の挙動を維持したまま4系にアップグレードすることができます。

この変更点に関して詳しい内容を知りたい場合は以下の記事やIssueを参照してください。

パフォーマンス改善等

パフォーマンス改善については以下の5点がNEWS.mdに掲載されています。

  • Inline cache mechanism is introduced for reading class variables. [Feature #17763]
  • instance_eval and instance_exec now only allocate a singleton class when required, avoiding extra objects and improving performance. [GH-5146]
  • The performance of Struct accessors is improved. [GH-5131]
  • mandatory_only? builtin special form to improve performance on builtin methods. [GH-5112]
  • Experimental feature Variable Width Allocation in the garbage collector. This feature is turned off by default and can be enabled by compiling Ruby with flag USE_RVARGC=1 set. [Feature #18045] [Feature #18239]

詳しい内容については上記の各リンクを参照してください。

JIT

コマンドオプションの意味が変わった

Ruby 3.1ではMJITに加えてYJITが実験的に導入されたため、コマンドオプションの意味が一部変わりました(以下はNEWS.mdの内容を意訳したものです)。

  • Ruby 3.0以前の--jitオプションは--mjitにリネームされた
  • Windows x86-64プラットフォームでは--jit--mjitのエイリアスになった
  • それ以外のプラットフォーム(macOS や Linux)では--jit--yjitのエイリアスになった

MJITの各種変更

MJITに関しては以下の4点がNEWS.mdに掲載されています。

  • The default --mjit-max-cache is changed from 100 to 10000.
  • JIT-ed code is no longer cancelled when a TracePoint for class events is enabled.
  • The JIT compiler no longer skips compilation of methods longer than 1000 instructions.
  • --mjit-verbose and --mjit-warning output "JIT cancel" when JIT-ed code is disabled because TracePoint or GC.compact is used.

YJITの実験的導入

Ruby 3.1ではYJITと呼ばれる新しいJITコンパイラが実験的に導入されました。YJITを有効化する方法や特長は以下のとおりです(以下はNEWS.mdの内容を意訳したものです)。

  • --yjitオプションを付けて実行するとYJITが有効化されます
  • YJITを使うとrailsbenchで最大22%、liquid-renderで39%高速化されます
  • 短時間でウォームアップが完了します
  • 現時点ではUnixライクな環境(macOS や Linux)のみをサポートします

YJITに関する詳しい情報は以下の資料を参考にしてください。

静的解析

RBS

(以下はNEWS.mdの内容を意訳したものです)

  • ジェネリックな型パラメータに制限が付けられるようになりました。(PR
# `T` は `_Output` インターフェースと互換性がなければいけない
# `PrettyPrint[String]` は良いが `PrettyPrint[Integer]` は型エラーとなる
class PrettyPrint[T < _Output]
  interface _Output
    def <<: (String) -> void
  end
  attr_reader output: T
  def initialize: (T output) -> void
end
  • ジェネリックな型エイリアスが定義できるようになりました。(PR
# ジェネリックな `list` 型を定義する
type list[T] = [ T, list[T] ] | nil

type str_list = list[String]
type int_list = list[Integer]
  • gemのRBSを管理するためにrbs collectionが導入されました。
  • 組み込みライブラリや標準ライブラリのシグニチャが多数追加・更新されました。
  • 多数のバグ修正やパフォーマンス改善が行われました。

詳細はRBSのCHANGELOG.mdを参考にしてください。

TypeProf

(以下はNEWS.mdの内容を意訳したものです)

TypeProfのIDEサポート機能についてはRubyKaigi Takeout 2021の以下のスライドや動画も参照してください。

デバッガ

(以下はNEWS.mdの内容を意訳したものです)

Ruby 3.1では新しいデバッガとしてdebug.gemが同梱されました。debug.gemは高速で、リモートデバッグや色づけされたREPL、IDE(VS Code)統合機能といった様々な機能を提供します。これまで使われていた標準ライブラリのlib/debug.rbはdebug.gemの置き換えられました。

デバッグ実行を開始・制御するためのrdbgコマンドもbin/ディレクトリにインストールされます。

debug.gemに関して詳しい情報を知りたい場合は以下のような記事を参考にしてみてください。

エラーハイライト(error_highlight)

(以下はNEWS.mdの内容を意訳&サンプルコードを一部改変したものです)

Ruby 3.1ではerror_highlightと呼ばれるビルトインgemが導入されました。このgemはバックトレース内でエラーの発生箇所を詳細に表示します。

以下はサンプルコードとエラーの表示例です。

json = nil

title = json[:article][:title]
#=> lib/sample.rb:3:in `<main>': undefined method `[]' for nil:NilClass (NoMethodError)
#   
#   title = json[:article][:title]
#               ^^^^^^^^^^
json = {article: nil}

title = json[:article][:title]
#=> lib/sample.rb:3:in `<main>': undefined method `[]' for nil:NilClass (NoMethodError)
#   
#   title = json[:article][:title]
#                         ^^^^^^^^

この機能はデフォルトで有効化されています。無効化する場合は--disable-error_highlightというコマンドラインオプションを付けます。詳しい使い方はerror_highlightのリポジトリを参照してください。

エラーハイライトの制限事項

この記事を執筆している時点ではエラーハイライトには以下のような制限事項(known issue)があるようです。

  • 非Ascii文字(全角文字)が含まれるとハイライトがズレる
  • 1行の文字が長すぎるとうまくハイライトできない
  • irb上では動作しない(無効化される)

詳しい内容はそれぞれ以下のissueをご覧ください。

IRB

IRB上で自動補完とドキュメント表示ができるようになった

(以下はNEWS.mdの内容を意訳したものです)

IRBに自動補完(オートコンプリート)機能が実装されました。コードをタイプするとダイアログに自動補完の候補が表示されます。タブキーもしくはシフトタブキーで候補を上下に選択できます。

ドキュメントがインストールされていれば、補完候補を選択した際にドキュメントダイアログが自動補完ダイアログの隣に表示されます。表示されるのはドキュメントの一部ですが、Alt-Dでドキュメント全文を読むことができます。

Screen Shot 2021-12-25 at 18.14.58.png

その他の変更点

ファイナライザ内で例外が発生すると標準エラー出力に出力されるようになった

Ruby 3.1ではファイナライザ(オブジェクトが GC される時に呼び出される処理)内で例外が発生すると標準エラー出力にその内容が出力されるようになりました。

以下は筆者が実際に遭遇した例外メッセージの例です(参考)。

/Users/jnito/.rbenv/versions/3.1.0-preview1/bin/bundle: warning: Exception in finalizer #<Proc:0x000000010a01e6f8 /Users/jnito/.rbenv/versions/3.1.0-preview1/lib/ruby/gems/3.1.0/gems/vcr-6.0.0/lib/vcr/library_hooks/webmock.rb:36 (lambda)>
/Users/jnito/.rbenv/versions/3.1.0-preview1/lib/ruby/gems/3.1.0/gems/vcr-6.0.0/lib/vcr/library_hooks/webmock.rb:36:in `block in global_hook_disabled_requests': wrong number of arguments (given 1, expected 0) (ArgumentError)

この例外を表示しないようにするためには$VERBOSEnilをセットするか、-W0オプションを付けてプログラムを実行します。

# -W0オプションを付けて実行するコマンド例
RUBYOPT=-W0 bundle exec rspec

その他

NEWS.mdのMiscellaneous changes欄にはこのほかに以下の3点が掲載されています。

  • lib/objspace/trace.rb is added, which is a tool for tracing the object allocation. Just by requiring this file, tracing is started immediately. Just by Kernel#p, you can investigate where an object was created. Note that just requiring this file brings a large performance overhead. This is only for debugging purposes. Do not use this in production. [Feature #17762]
    (https://bugs.ruby-lang.org/issues/17798)]
  • ruby -run -e httpd displays URLs to access. [Feature #17847]
  • Add ruby -run -e colorize to colorize Ruby code using IRB::Color.colorize_code.

詳しい内容については上記の各リンクを参照してください。

公式リファレンスのURLが変わった

(2022.1.10追記)

Ruby 3.1から(正確には3.0から)公式リファレンスマニュアル(るりま)のURLが変わりました。

以前は"2.7.0"のようにURLにTEENYバージョンが含まれていましたが、Ruby 3.1では"3.1"のようにマイナーバージョンまでになりました。

なお、Ruby 3.0では"3.0.0"にアクセスすると"3.0"にリダイレクトされるようになっています。

https://docs.ruby-lang.org/ja/3.0.0/doc/index.html
↓ リダイレクト
https://docs.ruby-lang.org/ja/3.0/doc/index.html

まとめ

というわけで、この記事ではRuby 3.1の変更点と新機能をいろいろと紹介してみました。

個人的にはハッシュリテラルやキーワード引数で値が省略できるようになったのが、Ruby 3.1の一番の目玉じゃないかな〜と思っています(普段のコーディングが結構変わりそう、という意味で)。それと、irbもすごくリッチになっていて、ちょっとした動作確認がとても便利になりそうです。

今年も2021年のクリスマスにRuby 3.1を届けてくれたMatzさんやコミッタのみなさんに感謝したいと思います。どうもありがとうございました!
みなさんもぜひRuby 3.1の新機能を試してみてください😉

PR: 拙著「プロを目指す人のためのRuby入門」の改訂2版が発売されました🍒

Ruby 3.1がリリースされるちょっと手前、2021年12月2日に拙著「プロを目指す人のためのRuby入門」(通称・チェリー本)の改訂2版が発売されました。第1版の対象バージョンはRuby 2.4でしたが、改訂2版ではRuby 3.0をフルサポートしています。特に、Ruby 2.7から導入されたパターンマッチについては、新しく章を追加して基本から発展的な内容まで詳細に説明しています。

その他、改訂2版の変更点については以下のブログ記事で詳しく説明しています。

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

62
24
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
62
24

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?