Why does Ruby seem to hoist variable declarations from inside a case statement even if that code path is not executed?
When the Ruby parser sees the sequence identifier, equal-sign, value,
as in this expressionx = 1
it allocates space for a local variable called
x
. The creation of the
variable—not the assignment of a value to it, but the internal
creation of a variable—always takes place as a result of this kind of
expression, even if the code isn’t executed! Consider this example:if false
x = 1
end
p x # Output: nil
p y # Fatal Error: y is unknownThe assignment to
x
isn’t executed, because it’s wrapped in a failing
conditional test. But the Ruby parser sees the sequencex = 1
, from
which it deduces that the program involves a local variablex
. The
parser doesn’t care whetherx
is ever assigned a value. Its job is
just to scour the code for local variables for which space needs to
be allocated. The result is thatx
inhabits a strange kind of variable limbo.
It has been brought into being and initialized tonil
.
In that respect, it differs from a variable that has no existence at
all; as you can see in the example, examiningx
gives you the valuenil
, whereas trying to inspect the non-existent variabley
results
in a fatal error. But althoughx
exists, it has not played any role in
the program. It exists only as an artifact of the parsing process.
Well-Grounded Rubyist chapter 6.1.2
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.
Ruby: Variables defined in If/else statement are accessible outside of if/else?
Variables are local to a function, class or module defintion, a proc
, a block.
In ruby if
is an expression and the branches don't have their own scope.
Also note that whenever the parser sees a variable assignment, it will create a variable in the scope, even if that code path isn't executed:
def test
if false
a = 1
end
puts a
end
test
# ok, it's nil.
It's bit similar to JavaScript, though it doesn't hoist the variable to the top of the scope:
def test
puts a
a = 1
end
test
# NameError: undefined local variable or method `a' for ...
So even if what you were saying were true, it still wouldn't be nil
.
Changing params in an if structure causes error (undefined method '[]' for nil:NilClass) in Ruby on Rails
from Holger Just's comment above:
Ruby initializes a new local variable named params with nil (which in your case shadows the method of the same name) even if the code is not actually executed. Ruby does this for all potential assignments, irregardless if they are actually executed or not, as in Ruby, the creation of variable is a separate step from the assignment.
Strange meaning of || and ||= in Ruby (2.0, 1.9.3, jruby 1.7.4)
I don't know if it is desirable, but it comes from how Ruby parses the code. Whenever you have a piece of code that assigns a local variable, that local variable is assigned nil
even if that piece of code is not evaluated. In your code line 2:
baz || baz = 0
the first baz
returned an error because no such variable was assigned. Hence the assignment baz = 0
that follows it was not evaluated, but nevertheless it was parsed, so in the context to follow, a local variable baz
was created, and is initialized to nil
.
With your second code chunk, foo
is not assigned during true if foo
and foo
. After that, foo = (true if foo)
has an assignment to foo
, so even though (true if foo)
is evaluated before assigment of foo
, an error is not raised in that line.
Variable getting initialized with nil
"[A local variable] is initialized if it appears on the left‐hand side (before the equals sign (U+003D)) of an assignment expression, even if the expression does not actually execute. Variables of the latter sort have the value nil."
EDIT: This answer used to point to a fairly good Ruby reference, which has apparently been replaced by a malware site. I've removed the link but retained the quotation of the answer.
Related Topics
Combining Implicit Wait and Explicit Wait Together Results in Unexpected Wait Times
How to Parse Json With Ruby on Rails
What Is the Best Method of Handling Currency/Money
How to Implement Enums in Ruby
Disable Activerecord For Rails 4
Difference Between \A \Z and ^ $ in Ruby Regular Expressions
Rvm Installation Not Working: "Rvm Is Not a Function"
Sudo Gem Install' or 'Gem Install' and Gem Locations
No Such File to Load - Rubygems (Loaderror)
Your Ruby Version Is 2.0.0, But Your Gemfile Specified 2.1.0
What Does the Question Mark At the End of a Method Name Mean in Ruby
Installing Rubygems in Windows
Ssl_Connect Returned=1 Errno=0 State=Sslv3 Read Server Certificate B: Certificate Verify Failed
"Bin/Rails: No Such File or Directory" W/ Ruby 2 & Rails 4 on Heroku