Returning from ruby blocks outside of their original scope-Collection of common programming errors
I wanted to make something that looks like this:
def make_wrapped_block(&block)
puts "take_block:before"
func = lambda do
puts "Before calling make_wrapped_block's passed block"
block.call
puts "After calling make_wrapped_block's passed block"
end
puts "take block:after"
func
end
def make_block
make_wrapped_block do
puts "Before return"
return :pi
puts "After return"
end
end
make_block.call
..where there would be many make_block
methods which generate closures with similar initialization and cleanup provided by make_wrapped_block
.
Because the block passed to make_wrapped_block
in make_block
returns, this causes a LocalJumpError:
[ cpm juno ~/tmp/local-jump ] ruby bad.rb
take_block:before
take block:after
Before calling make_wrapped_block's passed block
Before return
bad.rb:15:in `make_block': unexpected return (LocalJumpError)
from bad.rb:5:in `call'
from bad.rb:5:in `make_wrapped_block'
from bad.rb:20:in `call'
from bad.rb:20
Now, I can get this idea to work with a slightly different syntax:
def make_wrapped_block(block)
puts "take_block:before"
func = lambda do
puts "Before calling make_wrapped_block's passed block"
block.call
puts "After calling make_wrapped_block's passed block"
end
puts "take block:after"
func
end
def make_block
make_wrapped_block(lambda {
puts "Before return"
return :pi
puts "After return"
})
end
make_block.call
This works because when you return from an anonymous function created with lambda
, it exits the anonymous function, while with Proc.new
and anonymous blocks it tries to return from the scope it was defined in. You can’t pass them around and return safely.
Is there a safe way to return from passed blocks outside of the scope they were created?
The second way works well enough, but the syntax is a bit uglier than the first version.