On Sat, 20 Jan 2001, Bernd Lorbach wrote:

> Hi,

Hello,

> I don't understand what the ensure-clause is for.

I suggest you to do some testing where you try to exercise it a bit,
wonder a little and then ask again. But in this case, your example
below shows clearly you've done part of that already, so I'll show you
an example where the difference matters (and hopefully clarifies it).

> If you have
> 
> 	f = File.open ...
> 	begin
> 		...
> 	rescue
> 		puts "Error: #{$!}"
> 	ensure
> 		f.close
> 	end
> 
> you just could do
> 
> 	f = File.open ...
> 	begin
> 		...
> 	rescue
> 		puts "Error: #{$!}"
> 	end
> 	f.close
> 
> I think the latter one looks more symmetric and does just the same
> as the former version, as the rescue clause ensures, that the
> program is continued after the begin/end block.
> Am I missing something?

The thing is, the two examples of above code are not equivalent, and
in more general case even more care is needed to emulate ensure's
behaviour with plain rescues.

Try to run these examples:

  def go_away
    puts "exiting at go_away"
    exit
  end

  begin
    puts "raising"
    raise
    puts "going"
    go_away
    puts "went"
  rescue
    puts "rescued"
  ensure
    puts "ensured"
  end

  puts "exiting at end"
  exit

In this case the output is:

  raising
  rescued
  ensured
  exiting at end

which is pretty predictable. We raised before going into go_away, so
the exception was handled, _ensured_ and finally the program exited
naturally at the end.

Now the same with the unnatural exit at go_away (we just comment the
code raising an exception away):

  def go_away
    puts "exiting at go_away"
    exit
  end

  begin
#  puts "raising"
#  raise
    puts "going"
    go_away
    puts "went"
  rescue
    puts "rescued"
  ensure
    puts "ensured"
  end

  puts "exiting at end"
  exit

The output is radically different:

  going
  exiting at go_away
  ensured

Now, imagine your f.close was in place of 'puts "exiting at end"'. It
would not have get called (even thought the file would have been
closed when the clean-up of the object comes when Ruby shuts down).

But to get to the heart of the feature we have to modify the rescue
clause a little. Let's define the code to rescue all exceptions:

  def go_away
    puts "exiting at go_away"
    exit
  end

  begin
#  puts "raising"
#  raise
    puts "going"
    go_away
    puts "went"
  rescue Exception => e
    puts "rescued " + e.inspect
  ensure
    puts "ensured"
  end

  puts "exiting at end"
  exit

Now the output is:

  going
  exiting at go_away
  rescued SystemExit
  ensured
  exiting at end

So the thing is we just had not any rescue part for this type of
exception. Now that we have ensure is working along the lines of it.

In real life you want to write ensures, as your code communicates the
idea you want the file be closed after it has been used and you don't
want to go down and write a general exception handler, because it
makes your code inflexible, and probably requires a rewrite to
handlers logic when you make changes (add new exceptions or change
exception hierarcy).

If you want to emulate ensure, you have to add bunch of f.close if you
want the following code to close files in all rescuing clauses:

  class MyExit < SystemExit
  end

  for error in [Exception, SystemExit, MyExit]
    begin
      raise error
    rescue MyExit => e
      puts "rescued specifically MyExit: " + e.inspect
    rescue SystemExit => e
      puts "rescued specifically SystemExit: " + e.inspect
    rescue Exception => e
      puts "rescued generally " + e.inspect
    ensure
      puts "ensuring"
    end
  end

  puts "exiting at end"
  exit

outputting:

  rescued generally#<Exception: Exception>
  ensuring
  rescued specifically SystemExit: #<SystemExit: SystemExit>
  ensuring
  rescued specifically MyExit: #<MyExit: MyExit>
  ensuring
  exiting at end


or if you think reordering would help, you end up with even harder
problem (as only the general exception handler gets used):

  class MyExit < SystemExit
  end

  for error in [Exception, SystemExit, MyExit]
    begin
      raise error
    rescue Exception => e
      puts "rescued generally " + e.inspect
    rescue SystemExit => e
      puts "rescued specifically SystemExit: " + e.inspect
    rescue MyExit => e
      puts "rescued specifically MyExit: " + e.inspect
    ensure
      puts "ensuring"
    end
  end

  puts "exiting at end"
  exit

outputting

  rescued generally #<Exception: Exception>
  ensuring
  rescued generally #<SystemExit: SystemExit>
  ensuring
  rescued generally #<MyExit: MyExit>
  ensuring
  exiting at end

Adding some new exception classes afterwards and arranging the code
for them becomes trickier when the code, which has to be called in
every possible exit of the exception handling, has to be manually
found and injected in new handler.

So I hope it has became clear why it's beneficial to have a way in
exception handling to specify something to be run after which ever
exception handler was executed (or wasn't). It's just a short-hand for
not repeating yourself, but a very powerful one as it does not only
ease up your development but also prepares the code for the future
changes while maintains your original message what the code should do
to the next developer.

    - Aleksi