Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Ruby return in yield block called from a method with ensure

Tags:

ruby

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
like image 387
chifung7 Avatar asked Sep 29 '22 09:09

chifung7


People also ask

What does yield return in Ruby?

The yield keyword instructs Ruby to execute the code in the block. In this example, the block returns the string "yes!" . An explicit return statement was used in the cool() method, but this could have been implicit as well.

What is &Block in Ruby?

The &block is a way of sending a piece of Ruby code in to a method and then evaluating that code in the scope of that method.

What do you use the keyword yield for in Ruby?

yield is a keyword in Ruby which allow the developer to pass some argument to block from the yield, the number of the argument passed to the block has no limitations, the main advantage of using yield in Ruby, if we face any situation we wanted to our method perform different functions according to calling block, which ...

How do you define a method that can accept a block as an argument?

We can explicitly accept a block in a method by adding it as an argument using an ampersand parameter (usually called &block ). Since the block is now explicit, we can use the #call method directly on the resulting object instead of relying on yield .


1 Answers

It's a Ruby's feature of "unwinding the stack" from blocks. How your return works step by step:

  1. You return 3 from 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.
  2. But Ruby always executes ensure, and there is one more return in ensure section of foo.
  3. After 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.
  4. It returns 1 from 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.

like image 150
Oleg K. Avatar answered Oct 03 '22 00:10

Oleg K.