Travis
Travis

Reputation: 14446

Undefined local variable coerced to nil when being passed as argument

I mistakenly wrote a method that assigned a variable to the return value of a method call, which passed the same variable name as a parameter when it was previously undefined. I'm curious to know if this is a bug in Ruby, or actually intended behavior?

RUBY_VERSION
#=> "2.2.4"

def red(arg)
  arg
end

# this is expected since blue is not defined
blue
NameError: undefined local variable or method 'blue' for main:Object
from (pry):8:in '__pry__'

red(blue)
NameError: undefined local variable or method 'blue' for main:Object
from (pry):4:in '__pry__'

# here's the weird part
blue = red(blue)
#=> nil

blue
#=> nil

Upvotes: 3

Views: 335

Answers (2)

Todd A. Jacobs
Todd A. Jacobs

Reputation: 84373

Assignments May Implicitly Declare Variables

This behavior, while surprising, appears to be consistent across Ruby implementations including MRI, JRuby, and Rubinius. It is most likely a result of the parser treating the righthand side of the assignment as an expression that resolves to nil.

For example, this will raise an exception that informs you that blue is nil:

red = 1
blue = red + blue
# TypeError: nil can't be coerced into Fixnum

Because it's an assignment, the parser seems to implicitly declare blue as nil, which is then available on the righthand side as a nil value. Contrast this with a truly undeclared variable:

red = 1
blue = red + green
# NameError: undefined local variable or method `green' for main:Object

So, while this answer points you to some relevant documentation, it still doesn't really explain why this only applies to the righthand side of an assignment, and then only when the lvalue is implicitly declared within the rvalue. Since it's consistent across implementations, it seems to be expected behavior and is presumably required by the language's internals.

Asking why Matz and the Ruby Core Team designed the parser to treat this an edge case is a question for them. In practice, though, this language feature generally "does the right thing" for the programmer.

Upvotes: 0

user12341234
user12341234

Reputation: 7213

From "The Ruby Programming Language" by David Flanagan:

Ruby treats an identifier as a local variable if it has seen any previous assignment to the variable. It does this even if that assignment was never executed.

This means that variables are implicitly declared by the Ruby parser, even before any code gets executed. As soon as ruby sees the = operator, it immediately makes a new variable called yellow with a default value of nil. Then when that statement is executed, every reference to that variable share it's default value.

The book goes on to show an example of similar, perhaps surprising, behavior of variable declaration in ruby that is similar to this:

>> x               # NameError: undefined local variable or method `x'
>> x = 0 if false  # parsed, but never executed
>> p x             # displays nil

The significance of this code is that is shows that variables are declared as soon as code is parsed, and since your two references to yellow happen on the same line, the assignment of yellow must be parsed, and therefore declared, before it's passed as an argument to green

Upvotes: 5

Related Questions