Block scope in ruby Block scope in ruby ruby ruby

Block scope in ruby


This is expected behaviour for ruby 1.8. It was fixed in 1.9. Snippets below are run with ruby 1.9.3

food = ['toast', 'cheese', 'wine']food.each { |food| puts food.capitalize.inspect} # !> shadowing outer local variable - foodputs food.inspect# >> "Toast"# >> "Cheese"# >> "Wine"# >> ["toast", "cheese", "wine"]

You are correct, food from the block is scoped to that block and shadows other variables with this name. But if you do something destructive to it, it will be reflected in the original array, because it is reference to array element, not its copy. Observe:

food = ['toast', 'cheese', 'wine']food.each { |f| f.capitalize} # transform and discardfood # => ["toast", "cheese", "wine"]food.each { |f| f.capitalize! } # transform destructively (bang-version)food # => ["Toast", "Cheese", "Wine"]


The block inherits the scope from the context it is defined in. Take a look at this example:

def give_me_a_proc  test = "foo"  lambda { puts test }endtest = "bar"give_me_a_proc.call# => "foo"

So it doesn't matter where the proc/block is executed, but rather where it was defined.

In your case the food variable inside the block is indeed shadowing the food array from outside. But you really operate on the actual elements (not duplicates/clones) of food array in the block so any changes to them will be reflected outside.

The reason why food array changes into "Wine" string after the block is finished is that the block operates in the same scope the food array was defined, so it overwrites it with the last element of the array since it is the last object that is assigned to food variable.


This bug has been fixed in Ruby 1.9

food = ['toast', 'cheese', 'wine']food.each { |food| puts food.capitalize }puts food # ["toast", "cheese", "wine"]

I think if you want to use local variable in Ruby 1.8, try lambda (Sorry, I don't install Ruby 1.8, so can't test it, but work in 1.9)

food = ['toast', 'cheese', 'wine']lambda do |food|    food.each {|food| puts food.capitalize}end.call(food)puts food # ["toast", "cheese", "wine"]