Because Ruby relies so heavily on the use of blocks for iteration (while
and for
loops do exist in Ruby, but they're very rarely used by Ruby developers in practice) Ruby allows some keywords often associated with loops in other languages, such as break
, next
, and redo
, to be used inside of blocks. return
in Ruby also returns from the method the block is inside, which is consistent with the behavior of a loop, instead of from the block itself as functional programming languages do. This answer explains those behaviors quite nicely.
Now what I'm wondering is how this behavior works from the block caller's perspective (by block caller, I mean the method the block is being called from, as opposed to the method the block is defined in). For some of these keywords it's pretty self-explanatory (e.g. next
simply returns from the block, and redo
calls the block again with the same arguments), but for others the exact behavior is unclear to me. Specifically...
When I call break
from a block, does execution immediately get handed to the point after the block is defined? If I have some post-iteration cleanup steps to perform from within the iterator method, do they get completely skipped? E.g.:
def iterate
setup
1.upto(5) do |x|
yield x
end
cleanup # Does this get called? If not, how can I ensure that it does?
end
iterate do |x|
puts x # Prints 1
break # Break prevents further looping, somehow
end
# Execution continues here after break
What about with return
? I would assume it has similar behavior to that of break
in this context (whatever that behavior may be). Is that indeed the case?
What's even more confusing though is that Ruby blocks can be captured as method parameters and converted to Proc objects. These objects can then be called from anywhere, even outside the method that the block was originally defined in:
def get_proc(&block)
block
end
def get_iterator_proc
p = get_proc do |x| # Block is defined here...
puts x
break
end
# ...so `break` makes execution continue here...
do_other_stuff
return p
end
p = get_iterator_proc
p.call # ...even though the block isn't actually called until here?
And again, the same goes for return
. What does it actually return from in this context? How do these keywords really work?