Exception Causes in Ruby 2.1

Sometimes when rescuing an exception in Ruby, it’s useful to handle the error scenario by raising another, different exception. As an example, we may want to add domain-specific failure information before passing the error on to client code.

Exceptional Ruby book

The trouble with this technique is that it throws away all the information held by the original exception. This makes debugging harder, as there’s no stack trace to follow back to the root cause of the failure.

In Exceptional Ruby I demonstrated how to write nested exceptions in Ruby. A nested exception carries an optional field pointing back to an “original” exception. Here’s the example from the book:

The implementation of MyError uses a slightly sneaky trick. The initializer uses $! as the default value for original. $!, aka $ERROR_INFO, is a special Ruby variable which always points to the current exception if an exception is presently being raised. If no exception is being raised, it is nil.

This is why, in the example above, we’re able to raise MyError in the usual way, without explicitly initializing it with an original error. By defaulting to $!, the MyError initializer automatically picks up original from the environment.

Until today, a user-defined nested exception class such as this one was the only way to capture and retain information about an original exception that triggered a secondary exception. But today Ruby 2.1 dropped. One of the new features is a new method #cause on the base Exception class. #cause is automatically filled-in by raise (or fail), based on the value of $! just like our MyError implementation.

Let’s try it out:

This is just like our first example, except there’s no need for a special exception class, and we’ve changed .original to .cause.

Unlike MyError, there is no way to explicitly set Exception#cause. It is always implicitly filled in based on the environment it is raised in.

I’m pretty thrilled that this feature has finally made it into Ruby. Hand-rolled nested exception classes are useful for debugging, but they don’t do us any good when trying to debug errors raised in 3rd-party code that doesn’t use them. With the advent of Exception#cause, debugging Ruby exceptions just got a lot easier.