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
.