Gnawme
Gnawme

Reputation: 2399

Why is a Ruby class allowing access to an instance variable through a return from a method?

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

  1. Variables are references in Ruby, so temp = @internalOnly in the Leaky:foo method assigns a reference to @internalOnly
  2. Ruby returns the value of this expression -- the Array assigned to @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

Answers (1)

7stud
7stud

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

Related Questions