7

If I execute this ruby code:

def foo
  100
end

p defined?(foo), foo
if false
  foo = 200
end
p defined?(foo), foo

The output I get is:

"method"
100
"local-variable"
nil

Can someone explain to me why foo is set to nil after not executing the if? Is this expected behavior or a ruby bug?

Andrew Grimm
  • 78,473
  • 57
  • 200
  • 338
bmesuere
  • 502
  • 3
  • 12
  • 1
    The method is not overwritten and set to nil. `puts foo()` still works. What happens is that a local variable `foo` has come into existence. Sorry, but I cannot answer why this happens. – Mischa Dec 14 '11 at 14:23
  • I also can't answer why. But if there's ever an ambiguity between local variables and methods, be sure to use `self.foo` for the method, and `foo` for the variable. – Matthew Rudy Dec 14 '11 at 14:26
  • @sawa - I won't rollback again, but I don't understand your comment and I don't understand why you changed bmesuere's question. There is nothing wrong with the question. I don't see a need for editing it. – Mischa Dec 15 '11 at 06:31
  • @Mischa I changed three points. 1. `puts` does not output `nil`. It has to be `p`. 2. An extra level of defining a method and calling it is unnecessary. Removing that and putting it directly will make it reader friendly. 3. It is more natural and reader friendly to output the `defined?` value, which shows its category, then its value rather than in the other order. – sawa Dec 15 '11 at 06:40
  • 1
    @sawa - Your points are valid, I am just saying that the question is perfectly understandable without your edit. If it was a code review, I'd agree, but I don't see why you have to change the question. Besides, the way it is now, the output *bmesuere* gets, does not correspond with *your* code. – Mischa Dec 15 '11 at 06:50

2 Answers2

5

Names on the left hand side of assignments get set to nil, even if the code can't be reached as in the if false case.

>> foo
NameError: undefined local variable or method `foo' for main:Object
...
>> if false
..   foo = 1
..   end #=> nil
>> foo #=> nil

When Ruby tries to resolve barewords, it first looks for local variables (there's a reference to that in the Pickaxe book, which I can't seem to find at the moment). Since you now have one called foo it displays nil. As Mischa noted, the method still can be called as foo().

Michael Kohl
  • 66,324
  • 14
  • 138
  • 158
  • If I understand well, a local `foo` variable is created while ruby parses the code, even when that code might never be executed? – bmesuere Dec 14 '11 at 14:40
  • 2
    As long as the variable is on the left hand side of an assignment, yes. BTW: since you are new to SO, etiquette here is to upvote and/or accept helpful answers. If you have further questions, I'm happy to update my answer. – Michael Kohl Dec 14 '11 at 14:41
  • I'm sorry, I wasn't very clear in my previous comment. I meant to ask if the variable was only "reserved" while parsing, or if memory is also allocated (which seems terribly inefficient)? – bmesuere Dec 14 '11 at 14:51
  • 1
    The variables only a reference pointing to `nil`, which is a singleton object (there's only ever one instance of it). – Michael Kohl Dec 14 '11 at 15:16
1

This is what my pal and Ruby super-expert Josh Cheek had to say:

When Ruby sees the assignment, it initializes the variable in the current scope and sets it to nil. Since the assignment didn't get run, it didn't update the value of foo.

if statements don't change scope like blocks do. This is also the most important difference between

for x in xs

and

xs.each { |x| }

Here's another example:

a = 123 if a  # => nil
a  # => nil

We shouldn't be able to say if a because we never set a, but Ruby sees the a = 123 and initializes a, then gets to if a at which point a is nil

I'd consider it a quirk of the interpreter, really. Gary Bernhardt makes fun of it in wat (https://www.destroyallsoftware.com/talks/wat) with a = a

-Josh

Kori John Roys
  • 2,621
  • 1
  • 19
  • 27