Mutable member variables -- surprising behavior

I noticed some surprising behavior in one of my Ruby programs, and am
wondering what the rationale is. Basically,
given read-only access to a member variable (using attr_reader) of
class Array, I can modify that Array by modifying (what I think should
be) a local copy of it. Shouldn’t the accessor return a copy of the
variable instead of a reference to it? Or is it standard practice to
hand-write an accessor for arrays that returns a clone of the array?

#!/usr/bin/ruby

class Example
attr_reader :elems

def initialize
@elems = []
3.times { @elems << Object.new }
end

def to_s
@elems.inspect
end
end

e = Example.new
puts e

output is:

-604282308

[#Object:0xb7f6c450, #Object:0xb7f6c43c, #Object:0xb7f6c428]

list = e.elems
puts list.object_id

output is: -604282308

list.delete_at(0)

puts e

output is

-604282308

[#Object:0xb7f6c43c, #Object:0xb7f6c428]

On Mon, Apr 14, 2008 at 1:25 PM, Adam B. [email protected] wrote:

class Example
end
list.delete_at(0)

puts e

output is

-604282308

[#Object:0xb7f6c43c, #Object:0xb7f6c428]

You misunderstand what “attr_reader” does. The “attr_” methods simply
generate setter / getter methods on your class for instance variables.

attr_reader :variable

def variable; @variable; end

attr_writer :variable

def variable=(val); @variable = val; end

attr_accessor :variable creates both.

If you want something truely immutable, you have to #freeze it, but
then it’s not changeable inside the class either.

If you’re really worried about this, you can manually do:

class Example

def initialize
@elems = []
3.times { @elems << Object.new }
end

def to_s
@elems.inspect
end

Always give a copy of the array, that way the internal @elems

array is never changed.

def elems
@elems.clone
end
end

Hope that helps.

Jason R.

On Apr 14, 2008, at 11:34 AM, Jason R. wrote:

Always give a copy of the array, that way the internal @elems

array is never changed.

def elems
@elems.clone
end
end

does not work, neither does dup. both are rather shallow in ruby. if
you really want a copy you need

Marshal.load(Marshal.dump(@elems))

otherwise you’ll end up with subtle bugs when the array is cloned, but
elements inside of it, or deeper, are not.

fyi.

a @ http://codeforpeople.com/

On Apr 14, 11:25 am, Adam B. [email protected] wrote:

class Example
end
list.delete_at(0)

puts e

output is

-604282308

[#Object:0xb7f6c43c, #Object:0xb7f6c428]

This was recently discussed on this list. See:
http://groups.google.com/group/comp.lang.ruby/browse_thread/thread/18058b9f36bdd1a7/

But the short answer is: Yes, you’ll have to write your own accessor
if you don’t want this behavior. All attr_reader does is define the
accessor, which just returns the object. You don’t get a setter
method (elems=), but that doesn’t make the object immutable.

HTH,
Chris

On Apr 14, 2008, at 12:02 PM, Jason R. wrote:

Eep, yeah, I forgot about that. #dup and #clone won’t work, sorry for
that misinformation.

well they might :wink:

i’ve killed a lot of time over the years on that one ;-(

cheers.

a @ http://codeforpeople.com/

On Mon, Apr 14, 2008 at 1:43 PM, ara.t.howard [email protected]
wrote:

a @ http://codeforpeople.com/

we can deny everything, except that we have the possibility of being
better. simply reflect on that.
h.h. the 14th dalai lama

Eep, yeah, I forgot about that. #dup and #clone won’t work, sorry for
that misinformation.

Jason