I am getting NameError: undefined local variable or method
with ruby 2.1.2
As observed in this question, expressions like:
bar if bar = true
raises an undefined local variable error (provided that bar
is not defined prior) because bar
is read by the parser before it is assigned. And I believe that there used to be no difference with that with this expression:
bar if bar = false
The difference between the two is whether the main body is evaluated or not, but that should not matter if encountering an undefined local variable immediately raises an error before evaluating the condition.
But when I run the second code on Ruby 2.1.2, it does not raise an error. Has it been like that from before? If so, then what was the parsing discussion all about? If not, has Ruby specification changed? Is there any reference to that? What did it do in 1.8.7, 1.9.3, etc. ?
There is no difference with regards to whether bar
is defined or not. In both cases, bar
is undefined in the body. However, in the latter case, the body is never evaluated, therefore it doesn't matter. You never resolve the name bar
, therefore you never get an error during name resolution.
Local variables are defined when an assignment is parsed. They are initialized when an assignment is executed.
It's perfectly fine for a variable to be unitialized. It will just evaluate to nil
in that case:
if false
bar = 42
end
bar
# => nil
However, if the variable is undefined, then Ruby doesn't know whether a bare word is a local variable or a receiverless argumentless message send:
foo
# NameError: undefined local variable or method `foo'
# ^^^^^^^^^
# Ruby doesn't know whether it's a variable or a message send
Compare with:
foo()
# NoMethodError: undefined method `foo'
# ^^^^^^^^^^^^^
self.foo
# NoMethodError: undefined method `foo'
# ^^^^^^^^^^^^^
All together now:
foo()
# NoMethodError: undefined method `foo'
self.foo
# NoMethodError: undefined method `foo'
foo
# NameError: undefined local variable or method `foo'
if false
foo = 42
end
foo
# => nil
foo = :fortytwo
foo
# => :fortytwo
The trouble in this particular case is that the order in which the expressions are parsed (and thus the order in which variables are defined) does not match with the order in which the expressions are executed.
The assignment is executed first, which would make you assume that bar
would be defined in the body. But it isn't, because the body was parsed first and thus an I don't know whether this is a method or a variable node was inserted into the syntax tree before the assignment was ever seen.
However, if that node is never interpreted, i.e. the condition is false, then nothing bad will happen.
In 1.8.7
, 1.9.3
, 2.0.0
and even 2.1.1
I get 2 warnings and no errors:
2.0.0-p247 :007 > bar if bar = false
(irb):7: warning: found = in conditional, should be ==
=> nil
2.0.0-p247 :008 > bar if bar = true
(irb):8: warning: found = in conditional, should be ==
=> true
whereas in the 2.1.2
version you mention I get 2 warnings and 1 NameError
error.
2.1.2 :001 > bar if bar = true
(irb):1: warning: found = in conditional, should be ==
NameError: undefined local variable or method `bar' for main:Object
from (irb):1
from /home/durrantm/.rvm/rubies/ruby-2.1.2/bin/irb:11:in `<main>'
2.1.2 :002 > bar if bar = false
(irb):2: warning: found = in conditional, should be ==
=> nil
This is on my Ubuntu 14
My answer is based on Ruby 2.1.2.
Adding with @Jörg W Mittag answer.
Another commonly confusing case is when using a modifier if
:
p a if a = 0.zero? # => NameError: undefined local variable or method `a'
Rather than printing “true” you receive a NameError, “undefined local variable or method `a’”. Since ruby parses the bare a left of the if first and has not yet seen an assignment to a it assumes you wish to call a method. Ruby then sees the assignment to a and will assume you are referencing a local method.
The confusion comes from the out-of-order
execution of the expression. First the local variable is assigned-to then you attempt to call a nonexistent method.
Based on above explanation -
bar if bar = false
simply returns nil
, as the expression has been evaluated as false, the body of the code associated with the if
modifier wouldn't be executed. nil
is being returned by any block in Ruby, by default when there is no explicit default value.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With