📚

【bugs.ruby Advent Calender】Enumerator#next と StopIteration の話【14日目】

2024/12/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)#resultnil を返すからになります。

# このときに #result が 3 を返すわけではない
e = StopIteration.new(3)

# message には含まれるが #result は nil になる
pp e.message   # => "3"
pp e.result    # => nil

Enumerator#nextStopIteration の挙動も知らなかったんですが loop の方の挙動はもっと知りませんでした。

関連

GitHubで編集を提案

Discussion