50

In this example,

def foo(x)
  if(x > 5)
    bar = 100
  end
  puts bar
end

Then foo(6) Outputs: 100 and foo(3) outputs nothing.

However if i changed the definition to

def foo(x)
  if(x > 5)
    bar = 100
  end
  puts bob
end

I get an "undefined local variable or method" error.

So my question is why I am not getting this error when I call foo(3) and bar is never set?

k107
  • 15,882
  • 11
  • 61
  • 59
rebo
  • 1,067
  • 1
  • 7
  • 10

5 Answers5

56

There a couple of things going on here. First, variables declared inside the if block have the same local scope as variables declared at the top level of the method, which is why bar is available outside the if. Second, you're getting that error because bob is being referenced straight out of the blue. The Ruby interpreter has never seen it and never seen it initialized before. It has, however, seen bar initialized before, inside the if statement. So when is gets to bar it knows it exists. Combine those two and that's your answer.

Todd
  • 3,438
  • 1
  • 27
  • 36
  • 2
    Thanks, yep I understood where the bob error just wasn't sure why I wasn't getting a bar error. Do you know if I can rely on this behaviour, is it part of the specification? For instance, can i check bar for nil after the if statement or should i really be also explicitly declaring bar = nil before the if statement? – rebo Nov 11 '10 at 13:55
  • 1
    Yes, this is reliable behavior. Declaring bar = nil would for sure be more explicit. But I don't know that most rubyists would do that, though. If you keep your methods small it shouldn't be hard to understand or see where bar came from. – Todd Nov 11 '10 at 14:28
15

Your second example is actually a red herring: the reason why you are getting an exception is not because bob is uninitialized, it's because it is ambiguous. It's impossible to tell whether it's a variable or a method.

Your first example works, because uninitialized local variables (as well as global variables and instance variables) evaluate to nil. Therefore, puts bar is perfectly fine: in one case bar is initialized to 100 and this evaluates to 100, in the other case it is uninitialized and thus evaluates to nil. puts calls to_s on its argument, which is defined for nil (it simply returns the empty string), so everything is fine and dandy.

See also In Ruby, why after starting irb, foo.nil? says undefined error, and @foo.nil? gives “true”, and @@wah.nil? gives error again?

Community
  • 1
  • 1
Jörg W Mittag
  • 363,080
  • 75
  • 446
  • 653
  • I don't know Ruby, but if this is correct, then the accepted answer by Todd presumably is not correct. He implies that the problem with bob is not that it is nil, but that it is uninitialized. The other answers suggest that you are correct. – MickeyfAgain_BeforeExitOfSO Mar 26 '12 at 23:47
  • @Jörg: Thank you so much, now you made it crystal clear to me. I wonder whether Matz is too busy to make himself as clear about this as you explained it now. Great answer! – Boris Stitnicky Jan 10 '13 at 14:22
4

So don't take this as gospel (since it is more based on observation then understanding), but it seems like the ruby interpreter will flag any word (without a sigil in front of it) to the left of an equals sign as a local. Your example is strange, this is even more bizarre

def foo
  bar = bar
  puts bar // nil, which gets coerced into ""
end

I don't understand why or how it works, but there you have it.

Matt Briggs
  • 41,224
  • 16
  • 95
  • 126
  • 1
    +1! JavaScript works the same way. It moves all declarations to the top and leaves assignments where they are. This can be a bit confusing. – jwueller Nov 11 '10 at 13:47
  • 2
    Everything on the LHS of an assignment will get initialized to nil: `>> if false ; test = whatever ; end ; test #=> nil`. This is also how stuff like `x ||= 5` works, because it means `x = x || 5` which will be `x = nil || 5` in case x wasn't previously defined. – Michael Kohl Nov 11 '10 at 15:56
  • @jwueller Ruby doesn't move all assignments to the top though. `puts b; b = b;` results in ``NameError: undefined local variable or method `b' for main:Object`` – Ajedi32 Jan 20 '15 at 19:45
  • @MichaelKohl Well, `x ||= 5` isn't necessarily equivalent to `x = x || 5` when `x` and `x=` are methods. E.g. `object.x ||= 5` (http://stackoverflow.com/a/27628662/1157054). For most cases though, you're correct. – Ajedi32 Jan 20 '15 at 19:50
2

foo(3) doesn't output nothing. It outputs a newline.

Using inspect would give you more of a hint:

def foo(x)
  if(x > 5)
    bar = 100
  end
  puts bar.inspect
end

foo(3)

prints out

nil

bar is a fully-fledged variable, which just happens to have a value of nil.

Andrew Grimm
  • 78,473
  • 57
  • 200
  • 338
0

I'm not sure what you're asking. Running foo(3) with the second definition will always give an error, since bob is never defined. The argument to the method doesn't change that.

Theo
  • 131,503
  • 21
  • 160
  • 205