📚
【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