I was surprised to read this in the Ruby Hacking Guide:

In Ruby exceptions come in the form of the function style method raise. raise is not a reserved word.

Wait, what?

raise is not a reserved word in Ruby?

It sure seems like part of the core structure of the language…

Let’s check the official list of Ruby keywords.

The following keywords start with R:

redo - Restarts execution in the current block. See control expressions

rescue - Starts an exception section of code in a begin block. See exception handling

retry - Retries an exception block. See exception handling

return - Exits a method. See methods. If met in top-level scope, immediately stops interpretation of the current file.

This is so weird. raise isn’t there.

Let’s test:

irb(main):001* def raise(beep)
irb(main):002*   puts "boop"
irb(main):003> end
=> :raise
irb(main):004> boop
boop
boop
boop
               rbboop
boop           ri                      █
boop           raise                   █
boop           rand
               raiboope
boop           raise
boop
irb(main):004> raise 2
boop
=> nil
irb(main):005> boop
boop
boop
boop
               rbboop
boop           ri                      █
boop           raise                   █
boop           rand
               raiboope
boop           raise
boop
irb(main):005> raise 6
boop
=> nil

So yes, raise is just a method, but redefine it at your own risk, because my simple definition here gets us deeply into Zalgo text behavior. It looks like IRB uses raise internally as part of its autocomplete functionality. It’s fun to see the side effect happening constantly while entering text.

If we redefine raise without any arguments, then the effect doesn’t occur. Presumably there is an internal ArgumentError before it enters the method body.

irb(main):001* def raise
irb(main):002*   puts "zalgo"
irb(main):003> end
=> :raise
irb(main):004> puts "test"
test
=> nil

OK, what else can you define here?

irb(main):001* def rescue
irb(main):002*   puts "help!"
irb(main):003> end
=> :rescue
irb(main):004> begin; raise "problem"; rescue => e; puts e.inspect; end
#<RuntimeError: problem>
=> nil
irb(main):005> send :rescue
help!
=> nil

So rescue is a valid method name, but, reasonably enough, the rescue keyword takes precedence over it if you do define it.

The same is true for other keywords - you can define them as methods if you want, but then you can’t call them normally.

irb(main):034* def while(condition)
irb(main):035*   loop do
irb(main):037*     break unless condition.call
irb(main):036*     yield
irb(main):038*   end
irb(main):039> end
=> :while
irb(main):040* send :while, -> { Kernel.rand < 0.5 } do
irb(main):041*   print "."
irb(main):042> end
# This prints a random number of dots and then halts.

Congratulations, we just reimplemented while without using while. Admittedly, it is useless, unintuitive, and dangerous if you do this in real application code. But in any case, there is no particular rule against using a reserved word for a method name.

Per the docs:

Method names may be one of the operators or must start a letter or a character with the eighth bit set. It may contain letters, numbers, an _ (underscore or low line) or a character with the eighth bit set. The convention is to use underscores to separate words in a multiword method name:

I’m imagining that in the Ruby parser implementation, whatever you type after def is parsed only as a Ruby method name and not interpreted as a possible keyword.

Meanwhile, raise is implemented as a method on the Kernel module that is included by Object, so that’s why it is both ubiquitous and stupid to redefine.

// ruby/eval.c

rb_define_global_function("raise", f_raise, -1);

// then f_raise delegates to rb_f_raise

static VALUE
f_raise(int c, VALUE *v, VALUE _)
{
    return rb_f_raise(c, v);
}

// which is implemented like this:
VALUE
rb_f_raise(int argc, VALUE *argv)
{
    VALUE cause = Qundef;
    argc = extract_raise_options(argc, argv, &cause);

    VALUE exception;

    // Bare re-raise case:
    if (argc == 0) {
        // Cause was extracted, but no arguments were provided:
        if (!UNDEF_P(cause)) {
            rb_raise(rb_eArgError, "only cause is given with no arguments");
        }

        // Otherwise, re-raise the current exception:
        exception = get_errinfo();
        if (!NIL_P(exception)) {
            argc = 1;
            argv = &exception;
        }
    }

    rb_raise_jump(rb_make_exception(argc, argv), cause);

    UNREACHABLE_RETURN(Qnil);
}

(It looks like it then uses rb_raise_jump to do the call stack mechanics of exception handling, since all we really see here is some argument parsing, but this isn’t the place to dig deeper into how it works.)

I do wonder why raise isn’t just a reserved word in the first place. I guess there must be a scenario where you would want to redefine it for some metaprogramming project, but I can’t think of a very sane use case.


Posted under: programming ruby