5

Consider the following two snippets of ruby code.

puts "One"
if false
  d = 1
end
puts "Two"
puts d
puts "Three"

This prints the following

One
Two

Three

Now, consider the following

[].each do |i|
  flag = false
end
puts "Two"
puts flag
puts "Three"

This gives the following

Two
'<main>': undefined local variable or method 'flag' for main:Object (NameError)

Why is it that in the first case a blank is printed and the 2nd case an error is thrown ?

Thanks

jimcgh
  • 5,637
  • 4
  • 27
  • 37
  • Possible duplicate of [I don't understand ruby local scope](http://stackoverflow.com/questions/4154864/i-dont-understand-ruby-local-scope) – Cara McCormack Nov 24 '15 at 18:27
  • 1
    Blocks create a new scope, therefore outside that scope the variable is not defined. `if` on the other hand - doesn't. When the interpreter sees a line where a variable will be assigned a value, it makes sure it is defined first and initializes it with `nil`. That is also why `foo = bar` will give you an error, while `baz = baz` will not. – ndnenkov Nov 24 '15 at 18:28
  • 2
    In second case, the `flag` variable is the scope of the block hence not visible outside (http://ruby-doc.org/docs/ruby-doc-bundle/UsersGuide/rg/localvars.html). In the first case though, the variable does get defined even if the code block has not executed - this part I am not sure about - may be Ruby interpreter marks it as defined – Wand Maker Nov 24 '15 at 18:30
  • In addition to what @ndn and Wand have said (sorry, Wand, both of you deserved the @ but rules are rules), referencing an initialized local variable normally raises an exception, but Ruby's parser finds it convenient to set `d` to `nil` in your first example, so no exception is raised, even though the line `d=1` is never executed. – Cary Swoveland Nov 24 '15 at 19:07

2 Answers2

6

The difference is that the if block isn't actually a separate scope like it is in other languages such as Java. Variables declared within the if block have the same scope as the surrounding environment. Now, in your case, that if block won't actually be executed, so you'd normally expect d to be undefined (resulting in the same error you got in the second example). But ruby is a little "smrt" in that the interpreter will set up a variable with that label the moment it sees it, regardless whether it is actually executed, because it essentially doesn't know just yet whether that branch will indeed execute. This is explained in "The Ruby Programming Language" by David Flanagan and Yukihiro Matsumoto (can't copy paste the text, adding screenshot instead): enter image description here

In the case of the .each loop, that do...end you've written in is actually a block, and it does have its own local scope. In other words, variables declared within a block are local to that block only.

However, blocks "inherit" the scope of the environment in which they're declared, so what you can do is declare flag outside of the .each iteration block, and then the block will be able to access it and set its value. Note that in the example you've given, that won't happen because you're attempting to iterate an empty array, but at the very least you won't receive an error any more.

Some additional reading:

Paul Richter
  • 10,908
  • 10
  • 52
  • 85
2

In Ruby when you do an assignment to a variable (in your case it's d) anywhere in False-branch of If-statement it declares this variable unless method d= is defined. Basically b = bla-bla-bla in False-branch makes this: b = nil.

When you use block on an empty array nothing happens. And if an array is not empty variable still be local for current iteration of a block unless it was defined outside block scope, for example:

[1,2,3,4].each do |i|
  a=i
end
puts a

NameError: undefined local variable or method `a' for main:Object

a=1
[1,2,3,4].each do |i|
  a=i
end
puts a

4

Also you have an option to use a as a local one inside a block if it has been previously defined:

a=1
[1,2,3,4].each do |i; a|
  a=i
end
puts a

1

Maxim Pontyushenko
  • 2,983
  • 2
  • 25
  • 36