619

I've recently started programming in Ruby, and I am looking at exception handling.

I was wondering if ensure was the Ruby equivalent of finally in C#? Should I have:

file = File.open("myFile.txt", "w")

begin
  file << "#{content} \n"
rescue
  #handle the error here
ensure
  file.close unless file.nil?
end

or should I do this?

#store the file
file = File.open("myFile.txt", "w")

begin
  file << "#{content} \n"
  file.close
rescue
  #handle the error here
ensure
  file.close unless file.nil?
end

Does ensure get called no matter what, even if an exception isn't raised?

the Tin Man
  • 158,662
  • 42
  • 215
  • 303
Lloyd Powell
  • 18,270
  • 17
  • 87
  • 123
  • 2
    Neither is good. As a rule, when dealing with external resources, you **always** want the resource opening` to be inside the `begin` block. – Nowaker Oct 04 '18 at 17:38

7 Answers7

1315

Yes, ensure ensures that the code is always evaluated. That's why it's called ensure. So, it is equivalent to Java's and C#'s finally.

The general flow of begin/rescue/else/ensure/end looks like this:

begin
  # something which might raise an exception
rescue SomeExceptionClass => some_variable
  # code that deals with some exception
rescue SomeOtherException => some_other_variable
  # code that deals with some other exception
else
  # code that runs only if *no* exception was raised
ensure
  # ensure that this code always runs, no matter what
  # does not change the final value of the block
end

You can leave out rescue, ensure or else. You can also leave out the variables in which case you won't be able to inspect the exception in your exception handling code. (Well, you can always use the global exception variable to access the last exception that was raised, but that's a little bit hacky.) And you can leave out the exception class, in which case all exceptions that inherit from StandardError will be caught. (Please note that this does not mean that all exceptions are caught, because there are exceptions which are instances of Exception but not StandardError. Mostly very severe exceptions that compromise the integrity of the program such as SystemStackError, NoMemoryError, SecurityError, NotImplementedError, LoadError, SyntaxError, ScriptError, Interrupt, SignalException or SystemExit.)

Some blocks form implicit exception blocks. For example, method definitions are implicitly also exception blocks, so instead of writing

def foo
  begin
    # ...
  rescue
    # ...
  end
end

you write just

def foo
  # ...
rescue
  # ...
end

or

def foo
  # ...
ensure
  # ...
end

The same applies to class definitions and module definitions.

However, in the specific case you are asking about, there is actually a much better idiom. In general, when you work with some resource which you need to clean up at the end, you do that by passing a block to a method which does all the cleanup for you. It's similar to a using block in C#, except that Ruby is actually powerful enough that you don't have to wait for the high priests of Microsoft to come down from the mountain and graciously change their compiler for you. In Ruby, you can just implement it yourself:

# This is what you want to do:
File.open('myFile.txt', 'w') do |file|
  file.puts content
end

# And this is how you might implement it:
def File.open(filename, mode='r', perm=nil, opt=nil)
  yield filehandle = new(filename, mode, perm, opt)
ensure
  filehandle&.close
end

And what do you know: this is already available in the core library as File.open. But it is a general pattern that you can use in your own code as well, for implementing any kind of resource cleanup (à la using in C#) or transactions or whatever else you might think of.

The only case where this doesn't work, if acquiring and releasing the resource are distributed over different parts of the program. But if it is localized, as in your example, then you can easily use these resource blocks.


BTW: in modern C#, using is actually superfluous, because you can implement Ruby-style resource blocks yourself:

class File
{
    static T open<T>(string filename, string mode, Func<File, T> block)
    {
        var handle = new File(filename, mode);
        try
        {
            return block(handle);
        }
        finally
        {
            handle.Dispose();
        }
    }
}

// Usage:

File.open("myFile.txt", "w", (file) =>
{
    file.WriteLine(contents);
});
Jörg W Mittag
  • 363,080
  • 75
  • 446
  • 653
  • 102
    Note that, although the `ensure` statements are executed last, they are not the return value. – Chris Dec 11 '13 at 18:47
  • 1
    Want to add that you can have a catch-all to display every exception type by doing this: `begin ... rescue Exception => e ... puts e.message; puts e.backtrace.inspect ... end`. – Automatico Mar 19 '14 at 14:35
  • 46
    I love seeing rich contributions like this on SO. It goes above and beyond what the OP asked such that it applies to many more developers, yet is still on topic. I learned a few things from this answer + edits. Thank you for not just writing "Yes, `ensure` gets called no matter what." – Dennis Oct 17 '14 at 11:11
  • 5
    Note, that ensure is NOT guaranteed to complete. Take the case of where you have a begin/ensure/end inside of a thread, and then you call Thread.kill when the first line of the ensure block is being called. This will cause the rest of the ensure to not execute. – Teddy Jan 06 '15 at 14:47
  • 7
    @Teddy: ensure is guaranteed to begin executing, not guaranteed to complete. Your example is overkill - a simple exception inside the ensure block will cause it to exit as well. – Martin Konecny May 04 '15 at 23:35
  • 1
    @MartinKonecny That's what I said... True though, my example is overkill. – Teddy May 11 '15 at 14:55
  • 3
    Please note that the C# example doesn't remove the need for `using`. The `open` method still needs to do cleanup. The example just does this the verbose (and not 100% bullet-proof) way instead of using the `using` shorthand. I recommend `using` whenever possible in place of `try-finally`. – Mashmagar Aug 19 '15 at 12:55
  • 3
    also note that there are no guarantees ensure is called. I'm serious. A power outage / hardware error / os crash can happen, and if your software is critical, that needs to be considered too. – EdvardM Oct 02 '15 at 16:12
  • +1 for "Some blocks form implicit exception blocks." I'd still be using 'begin' in method defs if it wasn't for reading this - & Rubocop :) – MatzFan Jun 10 '16 at 10:59
  • 3
    I don't know that it makes sense to say `using` is "superfluous;" its whole point is to be a nicer way of writing out the try - finally block you've written. – Casey Sep 21 '16 at 19:15
  • @Casey: what I'm getting at is that in a sufficiently powerful language, you can write `using` as a library method, and don't need a language construct. In fact, as of C♯ 3, you *can* write `using` as a library method taking an `Action` or `Func` (where `T : IDisposable`) and centralizing the `try`/`catch` in one single place in the library. I wrote a specific `File.open` method, but you could write a more generic `Object.using` method instead, which takes the resource as an argument. – Jörg W Mittag Sep 21 '16 at 19:20
  • 3
    Oh. Well this seems to me like trying to write Ruby in C# more than solving a real problem that the traditional C# model doesn't. – Casey Sep 21 '16 at 19:32
  • 3
    But the ensure statement can return a value if you want as in `def test begin 144/0 rescue StandardError =>e 77 ensure puts "in ensure" return 66 end end` this will return `66` not `77`, without the `return 66` the method returns `77` – ryan2johnson9 Mar 04 '20 at 05:22
  • Note that the `ensure` statements are executed even when the method implicitly or explicitly returns (counts towards "no matter what"). – Michael Franzl Sep 20 '21 at 13:25
  • Here is a case where `ensure` is not executed ``` class CustomException < StandardError; end begin nil.nil rescue CustomException => e puts "custom exception" ensure puts "never on earth" end ``` – gabriel Mar 27 '23 at 14:18
  • @gabriel: I get `never on earth` printed to stdout and then the exception message ``undefined method `nil' for nil:NilClass (NoMethodError)``, just as I would expect. – Jörg W Mittag Mar 28 '23 at 07:27
  • wow this brace style causes me physical pain – iconoclast Jul 27 '23 at 20:23
49

FYI, even if an exception is re-raised in the rescue section, the ensure block will be executed before the code execution continues to the next exception handler. For instance:

begin
  raise "Error!!"
rescue
  puts "test1"
  raise # Reraise exception
ensure
  puts "Ensure block"
end
the Tin Man
  • 158,662
  • 42
  • 215
  • 303
alup
  • 2,961
  • 1
  • 21
  • 12
15

If you want to ensure a file is closed you should use the block form of File.open:

File.open("myFile.txt", "w") do |file|
  begin
    file << "#{content} \n"
  rescue
  #handle the error here
  end
end
the Tin Man
  • 158,662
  • 42
  • 215
  • 303
Farrel
  • 2,383
  • 18
  • 14
  • 3
    I guess if you don't want to handle the error but just raise it, and close the file handle, you don't need the begin rescue here? – rogerdpack Feb 01 '13 at 21:34
8

This is why we need ensure:

def hoge
  begin
    raise
  rescue  
    raise # raise again
  ensure  
    puts 'ensure' # will be executed
  end  
  puts 'end of func' # never be executed
end  
the Tin Man
  • 158,662
  • 42
  • 215
  • 303
kuboon
  • 9,557
  • 3
  • 42
  • 32
7

Yes, ensure is called in any circumstances. For more information see "Exceptions, Catch, and Throw" of the Programming Ruby book and search for "ensure".

the Tin Man
  • 158,662
  • 42
  • 215
  • 303
Milan Novota
  • 15,506
  • 7
  • 54
  • 62
5

Yes, ensure like finally guarantees that the block will be executed. This is very useful for making sure that critical resources are protected e.g. closing a file handle on error, or releasing a mutex.

Chris McCauley
  • 25,824
  • 8
  • 48
  • 65
  • 1
    Except in his/her case, there's no guarantee for the file to be closed, because `File.open` part is NOT inside the begin-ensure block. Only `file.close` is but it's not enough. – Nowaker Oct 04 '18 at 17:39
5

Yes, ensure ENSURES it is run every time, so you don't need the file.close in the begin block.

By the way, a good way to test is to do:

begin
  # Raise an error here
  raise "Error!!"
rescue
  #handle the error here
ensure
  p "=========inside ensure block"
end

You can test to see if "=========inside ensure block" will be printed out when there is an exception. Then you can comment out the statement that raises the error and see if the ensure statement is executed by seeing if anything gets printed out.

the Tin Man
  • 158,662
  • 42
  • 215
  • 303
Aaron Qian
  • 4,477
  • 2
  • 24
  • 27