Why can I refer to a variable outside of an if/unless/case statement that never ran?

It's because of how the Ruby parser works. Variables are defined by the parser, which walks through the code line-by-line, regardless of whether it will actually be executed.

Once the parser sees x =, it defines the local variable x (with value nil) henceforth in the current scope. Since if/unless/case/for/while do not create a new scope, x is defined and available outside the code block. And since the inner block is never evaluated as the conditional is false, x is not assigned to (and is thus nil).

Here's a similar example:

defined?(x) and x = 0
x  #=> nil

Note that this is a rather high-level overview of what happens, and isn't necessarily exactly how the parser works.


This has to do with a quirk of Ruby's scoping rules.

In ruby, an undecorated variable x appearing by itself could either be a local variable or a method call -- the grammar can't tell which. It's up to the parser to figure it out as it resolves local variable references. The rule is simple: if an assignment to a variable of the same name has been seen already in the local scope, then the reference is a local variable, and the reference is bound to that local variable. Otherwise, it's a method call, and it will be looked up as such at runtime.

Local variable references in Ruby are optimized into array lookups (each local variable is assigned a 'slot', and bound local variable references generated by the parser are converted into slot references). The array is initialized with all nil:

/* initialize local variables */
for (i=0; i < local_size; i++) {
    *sp++ = Qnil;
}

Thus, if you refer to a local variable that hasn't been assigned, through a bound local reference (which can only happen if there was a skipped assignment above the reference in the same local scope), you get nil.

Tags:

Ruby