Project

General

Profile

Actions

Feature #20444

closed

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

Added by esad (Esad Hajdarevic) 11 days ago. Updated 8 days ago.

Status:
Feedback
Assignee:
-
Target version:
-
[ruby-core:117646]

Description

There was a https://bugs.ruby-lang.org/issues/11498 a while ago which was merged in, but I was surprised to find out that raising StopIteration in a loop like

loop { raise StopIteration.new(3) }

returns nil and not 3.

Updated by nobu (Nobuyoshi Nakada) 11 days ago

  • Status changed from Open to Closed

StopIteration.new(3) does not set result, and no way to set it in Ruby level.

$ ruby -e 'e = StopIteration.new(3); p e.message, e.result'
"3"
nil

Updated by esad (Esad Hajdarevic) 11 days ago

nobu (Nobuyoshi Nakada) wrote in #note-1:

StopIteration.new(3) does not set result, and no way to set it in Ruby level.

$ ruby -e 'e = StopIteration.new(3); p e.message, e.result'
"3"
nil

Thanks for the hint. It seems that subclassing StopIteration to provide result works:

class MyException < StopIteration
  def result = 5
end

loop { raise MyException } # => 5

Updated by nobu (Nobuyoshi Nakada) 10 days ago

I'm curious what your use case is.

Although I don't know the reason why StopIteration#initialize does not have the argument for result, it would be difficult to change it now because of the backward compatibility.

Actions #4

Updated by nobu (Nobuyoshi Nakada) 10 days ago

  • Status changed from Closed to Feedback
Actions #5

Updated by nobu (Nobuyoshi Nakada) 10 days ago

  • Tracker changed from Bug to Feature
  • ruby -v deleted (ruby 3.3.0 (2023-12-25 revision 5124f9ac75) [arm64-darwin20])
  • Backport deleted (3.0: UNKNOWN, 3.1: UNKNOWN, 3.2: UNKNOWN, 3.3: UNKNOWN)

Updated by esad (Esad Hajdarevic) 10 days ago

nobu (Nobuyoshi Nakada) wrote in #note-3:

I'm curious what your use case is.

Although I don't know the reason why StopIteration#initialize does not have the argument for result, it would be difficult to change it now because of the backward compatibility.

I think my use case is a bit of an edge case - I am passing a block into a Ractor where it runs in a loop. This way I can control exit from the loop, but obviously this can be also refactored into a normal result value of the block and "manual" looping control depending on the result.

Updated by ufuk (Ufuk Kayserilioglu) 10 days ago

@esad (Esad Hajdarevic) If you just want to return a result from the loop, you can use break <value> to do that:

$ ruby -e "puts loop { break 3 }"
3

You shouldn't have to deal with anything low level like StopIteration to do that.

Updated by esad (Esad Hajdarevic) 9 days ago ยท Edited

ufuk (Ufuk Kayserilioglu) wrote in #note-7:

@esad (Esad Hajdarevic) If you just want to return a result from the loop, you can use break <value> to do that:

$ ruby -e "puts loop { break 3 }"
3

Calling break from a block passed to a ractor will raise an exception. I think some sample code about my example will be helpful:

Let's make a ractor that just calls all blocks passed to it in a loop:

r = Ractor.new do 
  block = Ractor.receive
  loop { block.call() }
end

Now let's send it a block that raises StopIteration:

block = true.instance_eval { proc { raise StopIteration, 3 } } # instance_eval in true gives us a "shareable" proc 
r.send Ractor.make_shareable(block)
r.take # => nil

Let's try with a subclass (this works)

class MyStop < ::StopIteration
  attr_reader :result
  def initialize(result)
    @result = result
  end
end

block = true.instance_eval { proc { raise MyStop, 3 } }
r.send Ractor.make_shareable(block)
r.take # => 3

Updated by nobu (Nobuyoshi Nakada) 8 days ago

That example does not need Ractor.

class MyStop < ::StopIteration
  attr_reader :result
  def initialize(result)
    @result = result
  end
end

block = proc {raise StopIteration, 3}
p Thread.start {loop {block.call}}.value #=> nil

block = proc { raise MyStop, 3 }
p Thread.start {loop {block.call}}.value #=> 3

Updated by esad (Esad Hajdarevic) 8 days ago

nobu (Nobuyoshi Nakada) wrote in #note-9:

That example does not need Ractor.

Yes, you are right, it actually doesn't need Thread either, and is simply about calling a block in a loop and how to break the loop from the called block:

proc { break 3}.then { |p| loop { p.call() }} raises exception, and proc { raise StopIteration, 3 }.then { |p| loop { p.call() }} returns nil, etc.

Actions

Also available in: Atom PDF

Like0
Like0Like0Like0Like0Like0Like0Like0Like0Like0Like0