Ruby 3.2 - 3.2 rc1 以降の変更
Ruby 3.2 アドベントカレンダーの最終日の記事です。
3.2 rc1 以降の変更
今年も 12/25 に無事 Ruby 3.2.0 がリリースされた 🎉
ということで、3.2 rc1 以降の変更を、3.2.0-rc1 の NEWS.md と 3.2.0 リリースの NEWS.md の差分からピックアップ。
3.2 rc1 でも動きが変わってたのに NEWS.md に記載されてなかったものも含む。
Data#with 追加
3.2 で新しく導入された Data クラスに、リリース3日前に with メソッドが追加された。
Data オブジェクトは immutable で値の変更はできないんだけど、with で一部の値を変更した新しい Data オブジェクトを返すことができるようになった。
Hoge = Data.define(:x, :y)
hoge = Hoge.new(x: 123, y: 456)
fuga = hoge.with(y: 789)
hoge #=> #<data Hoge x=123, y=456>
fuga #=> #<data Hoge x=123, y=789>
便利。
IO.new (IO.for_fd) に path オプション追加 / IO#path 追加
IO.for_fd でファイルディスクリプタから IO オブジェクトを作ることができるけど、そのときに path オプションでファイル名を指定することができるようになった。
今まではファイル名が不明なオブジェクトで path を呼ぶと例外が発生していた:
f = File.open("hoge.txt")
f2 = File.for_fd(f.fileno)
f2.path #=> File is unnamed (TMPFILE?) (IOError)
Ruby 3.2 では例外が発生せずに nil が返る:
f = File.open("hoge.txt")
f2 = File.for_fd(f.fileno)
f2.path #=> nil
for_fd に path を指定するとそれが返る:
f = File.open("hoge.txt")
f2 = File.for_fd(f.fileno, path: "fuga.data")
f2.path #=> "fuga.data"
この変更は File じゃなくて IO に対して行われたので、File だけじゃなくて IO 系のオブジェクト全部で path が使えるようになってる。
$stdin.path #=> "<STDIN>"
$stdout.path #=> "<STDIN>"
$stderr.path #=> "<STDERR>"
r, w = IO.pipe
r.path #=> nil
w.path #=> nil
TCPSocket.new("localhost", 25).path #=> nil
Regexp.linear_time? 追加
Feature #19194: Add Regexp.linear_time? - Ruby master - Ruby Issue Tracking System
Regexp.linear_time?
で正規表現のマッチング処理が線形かどうかを確認できるようになった。
Regexp.linear_time?(/a/) #=> true
Regexp.linear_time?(/(a)*\1/) #=> false
String#dedup 追加
文字列オブジェクトは単項演算子の -
で immutable な文字列を作ることができる。
s = 'abc'
x = -s
s.frozen? #=> false
x.frozen? #=> true
x.upcase! #=> can't modify frozen String: "abc" (FrozenError)
これと同じことをする dedup というメソッドが追加された。
s = 'abc'
x = s.dedup
s.frozen? #=> false
x.frozen? #=> true
x.upcase! #=> can't modify frozen String: "abc" (FrozenError)
Thread::Queue#pop, Thread::SizedQueue#pop, #push に timeout オプション追加
Feature #18774: Add Queue#pop(timeout:) - Ruby master - Ruby Issue Tracking System
Feature #18944: Add SizedQueue#push(timeout:) - Ruby master - Ruby Issue Tracking System
Queue#pop
に timeout オプションを追加するとタイムアウトして nil を返すようになった。
q = Queue.new
q.push 123
Thread.new{ sleep }
q.pop #=> 123
q.pop(timeout: 1) #=> nil
ちなみに上のコードを Ruby 3.1 で実行すると queue empty (ThreadError)
という例外が発生する。
Queue#pop
に真の引数を指定するとキューが空だと ThreadError になるんだけど、3.1 までの pop はキーワード引数を受け付けないので、Hash オブジェクトという引数として渡される。Hash オブジェクトは真偽値的には真なので、ThreadError になる。
SizedQueue#pop
も同じ。SizedQueue#push
もブロックされるので同様に timeout オプションが追加された。
Time.new の引数に文字列を指定可能
Feature #18033: Time.new to parse a string - Ruby master - Ruby Issue Tracking System
Time.new
に日時風文字列引数を渡すとパースしてくれるようになった。
Time.new('2022-12-25 01:23:45') #=> 2022-12-25 01:23:45 +0900
Time.new('2022-12-25T01:23:45Z') #=> 2022-12-25 01:23:45 UTC
便利。年月日時分秒がちゃんと区切られてる文字列の場合は Time.parse
使わなくてもよくなった。
20221225012345
みたいな文字列はダメ。
Time.new('20221225012345') #=> 20221225012345-01-01 00:00:00 +0900
require 'time'
Time.parse('20221225012345') #=> 2022-12-25 01:23:45 +0900
_
の連続の扱いが変わった
String#to_c で Bug #19087: String#to_c supports multiple "_" - Ruby master - Ruby Issue Tracking System
文字列を数値に変換する処理は、数値文字列中の _
は無視するんだけど、_
の連続は不正文字としてそこで評価が終わるようになってる。けど、Ruby 3.1 では String#to_c
だけは __
も無視するようになっていた。
'1__234'.to_i #=> 1
'1__234'.to_f #=> 1.0
'1__234'.to_r #=> (1/1)
'1__234'.to_c #=> (1234+0i)
Ruby 3.2 では、これを他に合わせて __
以降は処理しないようになった。
'1__234'.to_i #=> 1
'1__234'.to_f #=> 1.0
'1__234'.to_r #=> (1/1)
'1__234'.to_c #=> (1+0i)
ENV.clone がエラーになるようになった
Bug #17767: Cloned ENV
inconsistently returns ENV
or self
- Ruby master - Ruby Issue Tracking System
Ruby 3.1 では ENV.dup
はエラーになるのに ENV.clone
はエラーにならなかったのが、3.2 では ENV.dup
と同様にエラーになるようになった。
ENV.dup #=> Cannot dup ENV, use ENV.to_h to get a copy of ENV as a hash (TypeError)
ENV.clone #=> Cannot clone ENV, use ENV.to_h to get a copy of ENV as a hash (TypeError)
エラー終了時に例外メッセージをエスケープしないようになった
Ruby 3.1:
% ruby -e 'raise "hoge\0\1\a\b\e\f\n\r\s\t\u0002\v\x03fuga"'
-e:1:in `<main>': hoge\0\x01\a\b\e\f (RuntimeError)
\r \x02\v\x03fuga
Ruby 3.2:
% ruby -e 'raise "hoge\0\1\a\b\e\f\n\r\s\t\u0002\v\x03fuga"'
-e:1:in `<main>': hoge
RuntimeError)
fuga
今までも \n
(改行), \s
(空白), \t
(タブ) はエスケープされずにそのまま表示されていたぽい。
これにより、例外メッセージにエスケープコードを入れて、
% ruby -e 'raise "\e[31m赤いメッセージ\e[0m"'
-e:1:in `<main>': 赤いメッセージ (RuntimeError) ←「赤いメッセージ」部分は端末上で赤く表示される
みたいなことができるようになった。
今まではエスケープされてたのでこんな感じになってた。
% ruby -e 'raise "\e[31m赤いメッセージ\e[0m"'
-e:1:in `<main>': \e[31m赤いメッセージ\e[0m (RuntimeError)
クラス/モジュール定義時の名前空間の変更
モジュールを include した状態で、そのモジュール配下に存在するクラスと同名のクラスを class
で指定したときに、3.1 まではモジュール配下の既存クラスの変更になっていたが、3.2 では新たなクラスが作られるようになった。
Ruby 3.1:
module Hoge; class Fuga; end; end
include Hoge
p Fuga #=> Hoge::Fuga
class Fuga
p self #=> Hoge::Fuga
end
p Fuga #=> Hoge::Fuga
Ruby 3.2:
module Hoge; class Fuga; end; end
include Hoge
p Fuga #=> Hoge::Fuga
class Fuga
p self #=> Fuga
end
p Fuga #=> Fuga
ちなみにトップレベルでなければ 3.1 も 3.2 も動きは変わらない。というか 3.1 のトップレベルだけ動きがおかしかったのが直されたって感じぽい。
module Parent
module Hoge; class Fuga; end; end
include Hoge
p Fuga #=> Parent::Hoge::Fuga
class Fuga
p self #=> Parent::Fuga
end
p Fuga #=> Parent::Fuga
end
これで Ruby 3.2 アドベントカレンダーは終了。最終日に大盛りだった。
Ruby 3.2 の変更のすべてを調べたわけではないけど、まあだいたいはわかった気になれた。
個人的に面白いと思ったのは IO#timeout
とか Regexp.new
に文字列でオプション指定可能 とか Integer#ceildiv
とか Timeout.timeout がスレッドを浪費しない とかかなー。
ではよいお年を。
Discussion