はじめに
🎄🎄🎄この記事はRuby Advent Calendar 2021 25日目の記事です🎄🎄🎄
Rubyは毎年12月25日にアップデートされます。
Ruby 3.1については2021年12月25日に3.1.0が正式リリースされました。
この記事ではRuby 3.1で導入される変更点や新機能について、サンプルコード付きでできるだけわかりやすく紹介していきます。
ただし、しっかり内容を確認する時間が取れなかった機能に関してはNEWS.mdの記載内容を転記するだけに留めています
本記事の情報源
本記事は以下のような情報源をベースにして、記事を執筆しています。
- NEWS.md
- NEWS.mdに記載されている各種issueとそれに関連するdiff
- Ruby 3.1.0リリース
- プロと読み解く Ruby 3.1 NEWS - クックパッド開発者ブログ
- @pink_bangbi さんのブログやスライド
- その他、ネット上の情報源
動作確認した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
次のような順番で評価されます。
foo
bar
-
foo
の結果に対して[]=
が呼ばれる
Ruby 3.0以前のRubyでは多重代入はこの順序に従っていませんでした。たとえば以下のコードであれば、
foo[0], bar.baz = a, b
Ruby 3.0以前のRubyでは次のような順序で評価されていました。
a
b
foo
-
foo
の結果に対して[]=
が呼ばれる bar
-
bar
の結果に対してbaz=
が呼ばれる
Ruby 3.1からはこの順序が単一代入と同様に、左辺が先、右辺が後に評価されるようになります。
foo
bar
a
b
-
foo
の結果に対して[]=
が呼ばれる -
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.new
がin:
というキーワード引数でタイムゾーンを指定できるようになりました。
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.now
やTime.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 があります。
この表記を使うためには"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
usingaddress_resolve
hook. [Feature #17370] - Introduce non-blocking
Timeout.timeout
usingtimeout_after
hook. [Feature #17470] - Introduce new scheduler hooks
io_read
andio_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
この警告が出る場合は以下のようにinclude
をimport_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.suspend
とCoverage.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#initialize
(new
メソッド)のシグニチャは以下のようになっています。(公式リファレンスから抜粋)
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.load
をPsych.unsafe_load
に置き換えると、いったん3系の挙動を維持したまま4系にアップグレードすることができます。
この変更点に関して詳しい内容を知りたい場合は以下の記事やIssueを参照してください。
パフォーマンス改善等
パフォーマンス改善については以下の5点がNEWS.mdに掲載されています。
- Inline cache mechanism is introduced for reading class variables. [Feature #17763]
-
instance_eval
andinstance_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の内容を意訳したものです)
- 実験的なIDEサポート機能が追加されました。
- 多数のバグ修正やパフォーマンス改善が行われました。
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でドキュメント全文を読むことができます。
その他の変更点
ファイナライザ内で例外が発生すると標準エラー出力に出力されるようになった
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)
この例外を表示しないようにするためには$VERBOSE
にnil
をセットするか、-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が変わりました。
- OLD https://docs.ruby-lang.org/ja/2.7.0/doc/index.html
- NEW https://docs.ruby-lang.org/ja/3.1/doc/index.html
以前は"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以降で発生する記述内容との差異は、それぞれ以下の記事にまとめてあります。なので、多少バージョンが古くても安心して読んでいただけます😊