Reputation: 2621
def foo
puts "in foo"
s = yield
puts "s = #{s}"
return 2
ensure
puts "in ensure"
return 1
end
def bar
foo do
puts "in bar block"
return 3
end
return 4
end
[36] pry(main)> r = bar
in foo
in bar block
in ensure
=> 4
I'd expect r = 3 but it turns out it is r = 4. If I remove the ensure code, r = 3 is expected. Why is it?
def foo
puts "in foo"
s = yield
puts "s = #{s}"
return 2
end
r = bar
in foo
in bar block
=> 3
Upvotes: 5
Views: 1857
Reputation: 1549
It's a Ruby's feature of "unwinding the stack" from blocks. How your return works step by step:
bar
block. return_value
= 3 and Ruby marks that it is a return value from block, so it should unwind the stack and return 3 from parent function. It would not return to foo
at all if there was no ensure
section. It is very important difference between returning from functions and from blocks.ensure
, and there is one more return
in ensure
section of foo
.return 1
in ensure
section of foo
, return_value
is 1. It is not a value from block, so Ruby "forgets" about previous return 3
and forgets to return it from bar
.foo
and 4
from bar
.Moreover, if you write next 3
instead of return 3
in the block - it will return to foo
after yield
and execute puts "s = #{s}"; return 2
even without the ensure block. This is a magical Ruby feature for iterators and enumerators.
Upvotes: 5