Rubyʼs raise Exception.new or raise Exception — they're both the same

… and check why 5600+ Rails engineers read also this

Rubyʼs raise Exception.new or raise Exception — they’re both the same

TLDR: You can use raise Exception and raise Exception.new - they’re identical as a result and it’s 4 characters less.

In my previous post (OOP Refactoring: from a god class to smaller objects | Arkency Blog I’ve used some code with raising exceptions:

  def add_task(task)
    raise Duplicate if @tasks.include?(task)
    @tasks << task
  end

or

  def assign_task(task, developer)
    raise AlreadyAssigned if task.assigned?
    task.assign_developer
  end

The way I’m raising the exception here is that I raise it without calling .new. This may look as if I’m raising a class, not an object.

Some people asked me if this actually works and if it’s the same.

We already know the short answer, so let’s dig into why it’s the same. Is there some kind of magic involved?

One place to go is to check the raise (Kernel) - APIdock documentation.

raise(*args) public

With no arguments, raises the exception in $! or raises a RuntimeError if $! is nil. With a single String argument, raises a RuntimeError with the string as a message. Otherwise, the first parameter should be the name of an Exception class (or an object that returns an Exception object when sent an exception message). The optional second parameter sets the message associated with the exception, and the third parameter is an array of callback information. Exceptions are caught by the rescue clause of begin…end blocks.

OK, this gives some answer, but I prefer to check the code - let’s look at the Ruby implementation (in C…)

static VALUE
rb_f_raise(int argc, VALUE *argv)
{
    VALUE err;
    VALUE opts[raise_max_opt], *const cause = &opts[raise_opt_cause];

    argc = extract_raise_opts(argc, argv, opts);
    if (argc == 0) {
        if (*cause != Qundef) {
            rb_raise(rb_eArgError, "only cause is given with no arguments");
        }
        err = get_errinfo();
        if (!NIL_P(err)) {
            argc = 1;
            argv = &err;
        }
    }
    rb_raise_jump(rb_make_exception(argc, argv), *cause);

    UNREACHABLE_RETURN(Qnil);
}

BTW I’m wondering if it’s the first time, we’ve posted some C code on the Arkency materials.

In order to actually get the answer, I started exploring the C code, but my C reading is not the biggest skill I have. I prefer reading Ruby.

Let’s look at Rubinius - the Ruby implementation of Ruby.

  def raise(exc=undefined, msg=nil, trace=nil)
    Rubinius.synchronize(self) do
      return self unless @alive

      if undefined.equal? exc
        no_argument = true
        exc = active_exception
      end

      if exc.respond_to? :exception
        exc = exc.exception msg
        Kernel.raise TypeError, 'exception class/object expected' unless Exception === exc
        exc.set_backtrace trace if trace
      elsif no_argument
        exc = RuntimeError.exception nil
      elsif exc.kind_of? String
        exc = RuntimeError.exception exc
      else
        Kernel.raise TypeError, 'exception class/object expected'
      end

      if $DEBUG
        STDERR.puts "Exception: #{exc.message} (#{exc.class})"
      end

      if self == Thread.current
        Kernel.raise exc
      else
        Rubinius.invoke_primitive :thread_raise, self, exc
      end
    end
  end

rubinius/thread.rb at 75086f2b2cc92302b54176db0250ec6635adfcc8 · rubinius/rubinius · GitHub

The main thing here is if exc.respond_to? :exception. Does that mean that both the Exception class and the Exception instances have this method?

Let’s find out!

class Exception
  class << self
    alias_method :exception, :new
  end

  def exception(message=nil)
    if message
      unless message.equal? self
        # As strange as this might seem, this IS actually the protocol
        # that MRI implements for this. The explicit call to
        # Exception#initialize (via __initialize__) is exactly what MRI
        # does.
        e = clone
        Rubinius.privately { e.__initialize__(message) }
        return e
      end
    end

    self
  end
end

rubinius/exception.rb at 0296620da5ce252266cccf3574ae3e756ab144e6 · rubinius/rubinius · GitHub

This part is the main answer:

  class << self
    alias_method :exception, :new
  end

So, exception is just an alias to new, at the class level. At the object level it just returns self.

Thanks to Rubinius, for making it easier to explore such things!

You might also like