Why can I refer to a variable outside of an if/unless/case statement that never ran?
Solution 1:
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.
Solution 2:
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
.
Solution 3:
I thought your question was interesting so I tried to look it up and found this: I don't understand ruby local scope
The correct answer seems to be put Jorg.
Lets look at what happens when you try to access a variable that isn't initialized:
NameError: undefined local variable or method `UNDECLAREDVAR' for main:Object
The exception states that it is unavailable to evaluate whether a variable or method. The reason it doesn't throw the same exception is because uninitialized local variables are set to nil. So puts x
is a okay because the interpretor knows that x
is variable but uninitialized and not a method.