Reputation: 3373
I am studying Ruby, and typed a piece of code to experiment with block-scoped variables:
x = 10
3.times do |i; x|
x = x + 1
puts("inside block is #{x}")
end
puts("outside block is #{x}")
I would expect the x block variable to automatically capture the value of the x global variable, and then x inside the block to be 11 every time, and x outside the block to be protected, and stay at 10. This "shielding" action is what is described in many Ruby tutorials found throughout the web.
But instead, the script fails, telling me that x is nil and that it doesn't have a +
function. In other words, the x block variable hasn't been initialized with a value.
The exact code above is in a file called delete_me.rb
, and ran with:
ruby delete_me.rb
When I run the script, I get the following error:
delete_me.rb:3:in `block in <main>': undefined method `+' for nil:NilClass (NoMethodError)
from delete_me.rb:2:in `times'
from delete_me.rb:2:in `<main>'
How do I initialize the value of a block variable in Ruby?
Upvotes: 3
Views: 1347
Reputation: 6075
This is because in ruby blocks do not create a new scope BUT they do create variables in their lookup table.
Your block would have access to the global x BUT you are passing x as an argument to the block. When times iterates it passes the i and x is set to nil because it is not passed a variable. So in your block you have a new x that has a value of nil on each iteration. If you pass in any other named variable it will work.
x = 10
3.times do |i; y|
x = x + 1
puts("x block is #{x}")
end
puts("outside block is #{x}")
Or even better, don't define it with another argument unless you are wanting to override any globals
x = 10
3.times do |i|
x = x + 1
puts("x block is #{x}")
end
puts("outside block is #{x}")
Upvotes: 0
Reputation: 239270
I would expect the x block variable to automatically capture the value of the x global variable,
That is not how Ruby works, and there is no reason to expect it to behave that way. An inner variable never takes its value from a variable it is shadowing, in any language that I know of. That would be a horribly designed language, where unrelated outer context could be changed or introduced that breaks an inner scope in completely unanticipated ways. The entire purpose of scope is to prevent this kind of thing.
x
is nil
, because it's a newly introduced variable that you didn't assign a value to.
How do I initialize the value of a block variable in Ruby?
You can check if it is nil
, and then assign a value to it:
x = 10
3.times do |i; x|
x ||= 0
x = x + 1
puts("inside block is #{x}")
end
puts("outside block is #{x}")
Note that this loop prints "inside block is 1"
, three times. You're making a new x
and setting it to 0 each time the block is invoked. If you want to accumulate state while you iterate, this is the wrong way to go about it. You either want to not shadow the outer x
, or use a different Enumerable method like inject
or each.with_object
.
Upvotes: 4