This question already has an answer here:
- I don't understand ruby local scope 5 answers
I was playing with the assignment operation within if
blocks, and discovered the below result, which surprised me:
C:\>irb --simple-prompt
if false
x = 10
end
#=> nil
p x
nil
x.object_id
#=> 4
#=> nil
p y
NameError: undefined local variable or method `y' for main:Object
from (irb):5
from C:/Ruby193/bin/irb:12:in `<main>'
In the above code you can see that the x
local variable has been created even though it was only assigned to in the falsy if
block. I tried to to see the content of x
with p x
which forced me to believe that assignment was not done, but the x
variable exists. x.object_id
also proved that is the case.
Now my question is how that x
local variable was created even though the if
block entry point is closed forever intentionally?
I expected the output of p x
to be similar to the output from p y
. But instead I got a surprising answer from p x
.
Could someone explain to me how this concept works?
EDIT
No, here is another test. This is not the case with only local
variables. The same happened with instance
and class
variables also. See the below:
class Foo
def show
@X = 10 if false
p @X,"hi",@X.object_id
end
end
#=> nil
Foo.new.show
nil
"hi"
4
#=> [nil, "hi", 4]
class Foo
def self.show
@@X = 10 if false
p @@X,"hi",@@X.object_id
end
end
#=> nil
Foo.show
nil
"hi"
4
#=> [nil, "hi", 4]
Successful case :
class Foo
def self.show
@@X = 10 if true
p @@X,"hi",@@X.object_id
end
end
#=> nil
Foo.show
10
"hi"
4
#=> [10, "hi", 4]
Ruby always parses all of your code. It doesn't look at false as a sign to not parse what's inside, it evaluates it and sees that the code inside shouldn't be executed
Ruby has local variable "hoisting". If you have an assignment to a local variable anywhere within a method, then that variable exists everywhere within the method, even before the assignment, and even if the assignment is never actually executed. Before the variable is assigned, it has a value of
nil
.Edit:
The above is not quite correct. Ruby does have a form of variable hoisting in that it will define a local variable when a local variable assignment is present, but not executed. The variable will not be found to be defined at points in the method above where the assignment occurs, however.
In Ruby, local variables are defined by the parser when it first encounters an assignment, and are then in scope from that point on.
Here's a little demonstration:
As you can see, the local variable does exist on line 7 even though the assignment on line 4 was never executed. It was, however, parsed and that's why the local variable
foo
exists. But because the assignment was never executed, the variable is uninitialized and thus evaluates tonil
and not42
.In Ruby, most uninitialized or even non-existing variables evaluate to
nil
. This is true for local variables, instance variables and global variables:It is, however, not true for class hierarchy variables and constants:
This is a red herring:
The reason why you get an error here is not that unitialized local variables don't evaluate to
nil
, it is thatfnord
is ambiguous: it could be either an argument-less message send to the default receiver (i.e. equivalent toself.fnord()
) or an access to the local variablefnord
.In order to disambiguate that, you need to add a receiver or an argument list (even if empty) to tell Ruby that it is a message send:
or make sure that the parser (not the evaluator) parses (not executes) an assignment before the usage, to tell Ruby that it is a local variable:
And, of course,
nil
is an object (it is the only instance of classNilClass
) and thus has anobject_id
method.