On Mon, Apr 14, 2008 at 1:25 PM, Adam Bender <abender / gmail.com> wrote:
> 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>]
>
>

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.