Reputation: 2399
I have this Ruby code from a colleague:
class Leaky
def initialize
@internalOnly = [[1, 2, 3], [3, 4, 5]]
end
def foo
if 5 > 4
temp = @internalOnly
end
end
def printInternal
puts " here is my @internalOnly: #{@internalOnly}"
end
end
a = Leaky.new
a.printInternal
b = a.foo # 1) Gets Leaky:@internal!
b[1] = 666 # 2) Modifies it!
a.printInternal
It produces this output:
here is my @internalOnly: [[1, 2, 3], [3, 4, 5]]
here is my @internalOnly: [[1, 2, 3], 666]
In statement # 1)
, Ruby is apparently returning a reference to the Leaky
instance variable @internalOnly
, which it then uses to modify @internalOnly
in statement #2)
.
When Leaky:printInternal
is called, the (externally) modified value is displayed.
I understand that
temp = @internalOnly
in the Leaky:foo
method assigns a reference to @internalOnly
@internalOnly
After that, I don't understand how b = a.foo
ends up with a reference to @internalOnly
; this would seem to violate Ruby's object encapsulation in a big way. What is going on here?
I couldn't find anything in the Ruby Language FAQ that would enlighten me. (Maybe this is an inFAQ...)
Upvotes: 0
Views: 163
Reputation: 48609
1) foo() is just an accessor method with a strange name, no different than:
def internalOnly
@internalOnly
end
2) Any method in a class can access the private variables of an instance, i.e. its @variables.
3) Methods return the 'value' of the last statement executed.
4) The value of an assignment statement is the right hand side.
After that, I don't understand how b = a.foo ends up with a reference to @internalOnly
Variables are references in Ruby
Because a.foo returns a reference to the array, which gets assigned to b. As a result, the two variables @internalOnly and b reference the same array.
this would seem to violate Ruby's object encapsulation in a big way. What is going on here?
Well, the programmer allowed that to happen. If that wasn't intended, then the code can be rewritten like this:
def foo
if 5 > 4
@internalOnly.dup
end
end
But, you should know that any programmer with enough knowledge of ruby can always access an instance's private variables:
class Dog
attr_reader :friends
def initialize(friends)
@friends = friends
end
end
d = Dog.new(['cat', 'bird', 'mouse'])
x = d.instance_variable_get(:@friends)
x[0] = 'velociraptor'
p d.friends
--output:--
["velociraptor", "bird", "mouse"]
Upvotes: 5