📚
【bugs.ruby Advent Calender】Enumerator#next と StopIteration の話【14日目】
bugs.ruby Advent Calender 14日目の記事です。
これはなに
今年1年間通してみてきた bugs.ruby のチケットの中から気になったものを1つずつ取り上げていく Advent Calender です。
取り上げるチケットは基本的にこのブログで取り上げたものになります。
記事のまとめは ここを参照 してください。
[Bug #2044] Kernel#loop: returning the "result" value of StopIteration doesn't work when raised directly
Enumerator#next でイテレーションするときに『最後の要素だった場合』に #next を呼び出すと StopIteration の例外が発生します。
# 1, 2 とイテレーションする Enumerator を定義する
enum = Enumerator.new { |y|
y << 1
y << 2
:ok
}
begin
pp enum.next # => 1
pp enum.next # => 2
# 次の要素がないのでここで例外が発生する
enum.next
rescue StopIteration => e
# StopIteration#result は Enumerator.new のブロック引数の戻りを返す
pp "error: #{e.result}"
# => "error: ok"
end
この StopIteration 例外なんですが、実は Kernel.#loop でも参照されており『 #loop 内で StopIteration 例外が発生したら loop から抜けて StopIteration#result の値を返す』という挙動になります。
enum = Enumerator.new { |y|
y << 1
y << 2
:ok
}
result = loop {
# 最後の要素で #next を呼び出すと StopIteration が発生し loop が終了する
# loop の戻り値は StopIteration#result の値を返す
pp enum.next
}
pp result
# => :ok
このチケットでは次のように『意図的に StopIteration を発生させた場合に loop で意図する値が返ってこない』というバグ報告になります。
# この場合 3 を返すことを期待するが nil が返ってくる
result = loop {
raise StopIteration.new(3)
}
pp result
# => nil
これなんですが loop の戻り値は StopIteration#result を参照するんですが StopIteration.new(3) の #result は nil を返すからになります。
# このときに #result が 3 を返すわけではない
e = StopIteration.new(3)
# message には含まれるが #result は nil になる
pp e.message # => "3"
pp e.result # => nil
Enumerator#next や StopIteration の挙動も知らなかったんですが loop の方の挙動はもっと知りませんでした。
Discussion