Let’s look at this code:

def func1
  puts "one"
  return
  puts "two"
ensure
  puts "ensure"
end

How the result will look? Of course you’ll never see "two", but what about "ensure"?

[13] pry(main)> func1
one
ensure
nil
[14] pry(main)>

Even you return from a function without exceptions, ensure gets executed.

Another example:

def func2
  10.times do |num|
    begin
      puts "iteration #{num}"
      next if num.even?
      puts "after iteration #{num}"
    ensure
      puts "ensure"
    end
  end
end

And let’s look at result:

[18] pry(main)> func2
iteration 0
ensure 0
iteration 1
after iteration 1
ensure 1
iteration 2
ensure 2
iteration 3
after iteration 3
ensure 3
iteration 4
ensure 4
iteration 5
after iteration 5
ensure 5
iteration 6
ensure 6
iteration 7
after iteration 7
ensure 7
iteration 8
ensure 8
iteration 9
after iteration 9
ensure 9
10
[19] pry(main)>

It was very surprising for me in a contrast with Ruby’s “least surprise principle”. But this is how it works:

Marks the final, optional clause of a begin/end block, generally in cases where the block also contains a rescue clause. The code in the ensure clause is guaranteed to be executed, whether control flows to the rescue block or not.

Conclusion

Every time execution leaves begin block, ensure gets executed no matter what: exception, return, next, break, etc.