How is a local variable created even when IF condition evaluates to false in Ruby?
Local variables in ruby are created during parsing/compilation of code (not execution). They are lexically scoped, so a local variable is not visible before the line where it's assigned to.
defined?(foo) # => nil
if false
defined?(foo) # =>
foo = 'blah'
defined?(foo) # =>
end
defined?(foo) # => "local-variable"
foo # => nil
defined?(foo)
lines inside of if
return nothing, because they didn't run. The assignment wasn't executed as well. However, the compiler saw the assignment to local variable and created one (with default value of nil
).
This behaviour explains the trick from WAT talk:
a = a # => nil
Even though variable a
doesn't exist, it is created (and set to nil) right before this line, simply because there is an assignment expression in the code (target of which is yet unknown local variable). So by the time the right hand side of this expression is evaluated, a
exists.
Why is a variable declared when it appears in if false ?
Ruby handles assignments at the parser level. From the documentation:
The local variable is created when the parser encounters the
assignment, not when the assignment occurs:a = 0 if false # does not assign to a
p local_variables # prints [:a]
p a # prints nil
Confusion with the assignment operation inside a falsy `if` block
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:
foo # NameError: undefined local variable or method `foo' for main:Object
if false
foo = 42
end
foo # => nil
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 to nil
and not 42
.
In Ruby, most uninitialized or even non-existing variables evaluate to nil
. This is true for local variables, instance variables and global variables:
defined? foo #=> nil
local_variables #=> []
if false
foo = 42
end
defined? foo #=> 'local-variable'
local_variables #=> [:foo]
foo #=> nil
foo.nil? #=> true
defined? @bar #=> nil
instance_variables #=> []
@bar #=> nil
@bar.nil? #=> true
# warning: instance variable @bar not initialized
defined? $baz #=> nil
$baz #=> nil
# warning: global variable `$baz' not initialized
$baz.nil? #=> true
# warning: global variable `$baz' not initialized
It is, however, not true for class hierarchy variables and constants:
defined? @@wah #=> nil
@@wah
# NameError: uninitialized class variable @@wah in Object
defined? QUUX #=> nil
QUUX
# NameError: uninitialized constant Object::QUUX
This is a red herring:
defined? fnord #=> nil
local_variables #=> []
fnord
# NameError: undefined local variable or method `fnord' for main:Object
The reason why you get an error here is not that unitialized local variables don't evaluate to nil
, it is that fnord
is ambiguous: it could be either an argument-less message send to the default receiver (i.e. equivalent to self.fnord()
) or an access to the local variable fnord
.
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:
self.fnord
# NoMethodError: undefined method `fnord' for main:Object
fnord()
# NoMethodError: undefined method `fnord' for main:Object
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:
if false
fnord = 42
end
fnord #=> nil
And, of course, nil
is an object (it is the only instance of class NilClass
) and thus has an object_id
method.
Local variables within if statement
Referencing a
inside the if
has the effect of declaring it as a variable if there is no method a=
defined for the object.
Since Ruby does not require methods to be called using the same syntax as referencing a variable or assigning to one, it needs to make an assessment as to the nature of the token in question. If it could be a method call because a method with that name has been defined, then it will be interpreted as such. If no such method exists at the time the source is compiled, then it will be a variable by default.
Does the || operator evaluate the second argument even if the first argument is true?
This happens because the ruby interpreter defines a variable when it sees an assignment to it (but before it executes the actual line of code). You can read more about it in this answer.
Boolean OR (||
) expression will evaluate to the value of left hand expression if it is not nil
and not false
, else ||
will evaluate to the value of right hand expression.
In your example the ruby interpreter sees an assignment to a
and rr
(but it doesn't execute this line yet), and initializes (defines, creates) a
and rr
with nil
. Then it executes the ||
expression. In this ||
expression, a
is assigned to 10
and 10
is returned. r=20
is not evaluated, and rr
is not changed (it is still nil
). This is why in the next line rr
is nil
.
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.
Variable defined despite condition should prevent it
This is expected behavior in Ruby. Quote from the Ruby docs:
The local variable is created when the parser encounters the assignment, not when the assignment occurs:
a = 0 if false # does not assign to a
p local_variables # prints [:a]
p a # prints nil
Why is `a = a` `nil` in Ruby?
Ruby interpreter initializes a local variable with nil
when it sees an assignment to it. It initializes the local variable before it executes the assignment expression or even when the assignment is not reachable (as in the example below). This means your code initializes a
with nil
and then the expression a = nil
will evaluate to the right hand value.
a = 1 if false
a.nil? # => true
The first assignment expression is not executed, but a
is initialized with nil
.
You can find this behaviour documented in the Ruby assignment documentation.
Variable in else condition assumed nil value
The local variable is created when the parser encounters the
assignment, not when the assignment occurs:
=> foo
# NameError: undefined local variable or method `foo' for main:Object
=> if false
=> foo = "bar" # does not assign to foo
=> end
=> nil
=> foo
=> nil
Why does `x =! 5` return false?
This is because x =! 5
is being interpreted as x = (!5)
(!
has higer precedence than =
). In Ruby every object is true
except nil
and false
. 5
has truthy value which you are negating using the operator !
. So false
as result is being assigned to the local variable x
.
!
Called Logical NOT
Operator - is used to reverse the logical state of its operand. If a condition is true
, then Logical NOT
operator will make false
.
Related Topics
Deleting a Modified Object from a Set in a No-Op
Jekyll Serve Dependency Error - Could Not Open 'Lib Curl'
In Ruby Why Does Nil.Id Return 4
In Ruby, Can You Perform String Interpolation on Data Read from a File
Ruby Mocha: Is There an Equivalent to Rspec-Mocks' #And_Call_Original
Ruby Regex Error: Incompatible Encoding Regexp Match (Ascii-8Bit Regexp with Utf-8 String)
Invalid Gemspec -Illformed Requirement ["#<Yaml::Syck::Defaultkey:0Xb5F9C990> 3.2.0"]
How to Deal with Memory Leaks in Rmagick in Ruby
Heroku: Gemfile.Lock Is Required Issue
Is There a Cucumber Hook to Run Before and After Each Feature
Show Full Path Name of the Ruby File When It Get Loaded
Parse Email Addresses for "From" and "To" Fields in Ruby
What Does ::Myclass Ruby Scope Operator Do
Ruby-Debug19 on Ruby-1.9.3-Preview1