Given code:
class Foo
attr_reader :bar
def initialize
@bar = "abc"
if false
bar = "123"
end
p bar
end
end
Foo.new
The result is
nil
Why does p bar
in initialize
print nil
instead of abc
?
Try this:
class Foo
attr_reader :bar
def initialize
p "instance methods defined in Foo: #{self.methods(false)}"
@bar = "abc"
p "defined? @bar: #{defined? @bar}"
p "bar: #{bar}"
p "defined? bar: #{defined? bar}"
if false
bar = "123"
end
p "defined? bar, 2nd time: #{defined? bar}"
p "bar.nil? = #{bar.nil?}"
p "self.bar = #{self.bar}"
p "instance methods defined in Foo: #{self.class.instance_methods(false)}"
end
end
Foo.new
"instance methods defined in Foo: [:bar]"
"defined? @bar: instance-variable"
"bar: abc"
"defined? bar: method"
"defined? bar, 2nd time: local-variable"
"bar.nil? = true"
"self.bar = abc"
"instance methods defined in Foo: [:bar]"
The lines:
"defined? @bar: instance-variable"
"defined? bar: method"
show that @bar
is an instance variable and bar
is an instance method, namely the getter method for @bar
created by attr_reader :bar
. Before
if false
bar = "123"
end
is evaluated, Ruby peers into the if
clause. There she sees bar = "123"
. If invoked, this would assign the value "123"
to an uninitialized local variable bar
.
bar=
cannot be an instance method (e.g., a setter for @bar
) because any method whose name ends with an equals sign must be invoked on an explicit receiver. (It works that way to allow coders to use local variables that have the same names as instance variables, minus the leading @
.)
What is an "explicit" receiver? If Foo
had a public instance method buz
, you could write:
foo = Foo.new
foo.buz
foo
is an explicit receiver for the method buz
. To invoke buz
from within one of Foo
's instance methods, you could use an explicit receiver:
self.buz
or just write:
buz
in which case self
is the implicit receiver.
As bar=
can only be written with an explicit receiver, we would have write:
attr_writer :bar
...
self.bar = "123"
to invoke @bar
's setter.
Where were we? Ah, we just concluded that:
if false
bar = "123"
end
would assign a value to the local variable bar
if the if
clause were executed, regardless of whether there exists a method Foo#bar=
.
Because false
is, well, false
, the contents of the if
clause are not executed, so the value of bar
is not changed from nil
.
The important thing is that the local variable bar
and the instance variable @bar
are just as different from each other as are night
and @day
. We can easily show that as follows:
a = 'cat'
@a = 'dog'
a #=> "cat"
a = 'pig'
@a #=> "dog"