Ruby Exceptions -- Why "else"?

RubyException Handling

Ruby Problem Overview


I'm trying to understand exceptions in Ruby but I'm a little confused. The tutorial I'm using says that if an exception occurs that does not match any of the exceptions identified by the rescue statements, you can use an "else" to catch it:

begin  
# -  
rescue OneTypeOfException  
# -  
rescue AnotherTypeOfException  
# -  
else  
# Other exceptions
ensure
# Always will be executed
end

However, I also saw later in the tutorial "rescue" being used without an exception specified:

begin
    file = open("/unexistant_file")
    if file
         puts "File opened successfully"
    end
rescue
    file = STDIN
end
print file, "==", STDIN, "\n"

If you can do this, then do I ever need to use else? Or can I just use a generic rescue at the end like this?

begin  
# -  
rescue OneTypeOfException  
# -  
rescue AnotherTypeOfException  
# -  
rescue
# Other exceptions
ensure
# Always will be executed
end

Ruby Solutions


Solution 1 - Ruby

The else is for when the block completes without an exception thrown. The ensure is run whether the block completes successfully or not. Example:

begin
  puts "Hello, world!"
rescue
  puts "rescue"
else
  puts "else"
ensure
  puts "ensure"
end

This will print Hello, world!, then else, then ensure.

Solution 2 - Ruby

Here's a concrete use-case for else in a begin expression. Suppose you're writing automated tests, and you want to write a method that returns the error raised by a block. But you also want the test to fail if the block doesn't raise an error. You can do this:

def get_error_from(&block)
  begin
    block.call
  rescue => err
    err  # we want to return this
  else
    raise "No error was raised"
  end
end

Note that you can't move the raise inside the begin block, because it'll get rescued. Of course, there are other ways without using else, like checking whether err is nil after the end, but that's not as succinct.

Personally, I rarely use else in this way because I think it's rarely needed, but it does come in handy in those rare cases.

EDIT

Another use case occurred to me. Here's a typical begin/rescue:

begin
  do_something_that_may_raise_argument_error
  do_something_else_when_the_previous_line_doesnt_raise
rescue ArgumentError => e
  handle_the_error
end

Why is this less than ideal? Because the intent is to rescue when do_something_that_may_raise_argument_error raises ArgumentError, not when do_something_else_when_the_previous_line_doesnt_raise raises.

It's usually better to use begin/rescue to wrap the minimum code you want to protect from a raise, because otherwise:

  • you may mask bugs in the code that wasn't supposed to raise
  • the intention of rescue is harder to decipher. Someone (including your future self) may read the code and wonder "Which expression did I want to protect? It looks like expression ABC... but maybe expression DEF too???? What was the author intending?!" Refactoring becomes much more difficult.

You avoid those problems with this simple change:

begin
  do_something_that_may_raise_argument_error
rescue ArgumentError => e
  handle_the_error
else
  do_something_else_when_the_previous_line_doesnt_raise
end

Solution 3 - Ruby

The else block in a begin rescue end block is used when you are perhaps expecting an exception of some sort to occur. If you run through all of your expected exceptions but still have nothing raised, then in your else block you can do whatever's needed now that you know that your original code ran error free.

Solution 4 - Ruby

The only reason I can see for the else block is if you want to execute something before the ensure block when the code in the begin block didn't raise any errors.

begin
  puts "Hello"
rescue
  puts "Error"
else
  puts "Success"
ensure
  puts "my old friend"
  puts "I've come to talk with you again."
end

Solution 5 - Ruby

Thanks to else you sometimes can merge two nested begin end blocks.
So (simplified example from my current code) instead of:

  begin
    html = begin
      NetHTTPUtils.request_data url
    rescue NetHTTPUtils::Error => e
      raise unless 503 == e.code
      sleep 60
      retry
    end
    redo unless html["market"]
  end

you write:

  begin
    html = NetHTTPUtils.request_data url
  rescue NetHTTPUtils::Error => e
    raise unless 503 == e.code
    sleep 60
    retry
  else
    redo unless html["market"]
  end

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestionKvassView Question on Stackoverflow
Solution 1 - RubyChris Jester-YoungView Answer on Stackoverflow
Solution 2 - RubyKelvinView Answer on Stackoverflow
Solution 3 - RubycmwrightView Answer on Stackoverflow
Solution 4 - RubyMagneView Answer on Stackoverflow
Solution 5 - RubyNakilonView Answer on Stackoverflow