🔥

[Bug #20444] loop ないで StopIteration 例外を発生させたときに loop が意図する値を返さないバグ報告

2024/04/23に公開

[Bug #2044] Kernel#loop: returning the "result" value of StopIteration doesn't work when raised directly

  • Enumerator#next でイテレーションするときに『最後の要素』で #next を呼び出すとStopIteration の例外が発生する
  • またこのときに StopIteration#result で『例外を発生させる原因となったメソッドなどの戻り値』を受け取る事ができる
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
  • これなんですが StopIteration.new(3)#result の戻り値が nil になるので期待する挙動になる
e = StopIteration.new(3)

# message には含まれるが #result は nil になる
pp e.message   # => "3"
pp e.result    # => nil
  • また Ruby レイヤーでは StopIteration#result を設定する事はできないとのこと
  • loop ほとんど使ったことがないのでこういう挙動があるのは知らなかった
GitHubで編集を提案

Discussion