0

I came around this strange feature(?) of arrays in Ruby and it would be very helpful if someone could explain to me why they work the way they do.

First lets give an example of how things usually work.

a = "Hello" #=> "Hello"
b = a #=> "Hello"
b += " Goodbye" #=> "Hello Goodbye"
b #=> "Hello Goodbye"
a #=> "Hello"

Ok cool, when you use = it creats a copy of the object (this time a string).

But when you use arrays this happens:

a = [1,2,3] #=> [1,2,3]
b = a #=> [1,2,3]
b[1] = 5 #=> [1,5,3]
b #=> [1,5,3]
a #=> [1,5,3]

Now thats just strange. Its the only object I've found that doesn't get copied when using = but instead just creates a refrance to the original object.

Can someone also explain (there must be a method) for copying an array without having it point back to the original object?

sawa
  • 165,429
  • 45
  • 277
  • 381
Eric Koslow
  • 2,004
  • 2
  • 21
  • 33

3 Answers3

11

Actually, you should re-examine your premise.

The string assignment is really b = b + " Goodbye". The b + " Goodbye" operation returns an entirely new string, so the variable b is pointing to a new object after the assignment.

But when you assign to an individual array element, you are not creating an entirely new array, so a and b continue to point to the same object, which you just changed.

If you are looking for a rationale for the mutating vs functional behavior of arrays, it's simple. There is nothing to be gained by modifying the string. It is most likely necessary to allocate new memory anyway, so an entirely new string is created.

But an array can be arbitrarily large. Creating a new array in order to change just one element could be hugely expensive. And in any case, an array is like any other composite object. Changing an individual attribute does not necessarily affect any other attributes.

And to answer your question, you can always do:

b = a.dup
DigitalRoss
  • 143,651
  • 25
  • 248
  • 329
1

What happends there is that ruby is treating the Array object by reference and not by value.

So you can see it as this:

 b= [1,2,3]
 a= b

              --'b' Points to---> [1,2,3] <--'a' points t---

So as you can see both point to the same reference, that means that if you change anything in a it will be reflected on b.

As for your question on the copying the object you could use the Object#clone method to do so.

Wifi Cordon
  • 226
  • 1
  • 6
  • .clone, doesn't seem to work for a two demential array. I'm assuming it clones the outer array, but keeps the inner arrays pointing to the original objects. Any ideas what I would do there? – Eric Koslow Dec 14 '10 at 15:14
1

Try your Array case with a String:

a = "Hello" #=> "Hello"
b = a #=> "Hello"
b[1] = "x" #=> "x"
b #=> "Hxllo"
a #=> "Hxllo"

Strings and Arrays work the same way in this regard.

The key difference in the two cases, as you wrote them, is this:

b += " Goodbye"

This is syntactic shorthand for

b = b + " Goodbye"

which is creating a new string from b + " Goodbye" and then assigning that to b. The way to modify an existing string, rather than creating a new one, is

b << " Goodbye"

And if you plug that into your sequence, you'll see that it modifies both a and b, since both variables refer to the same string object.

As for deep copying, there's a decent piece about it here:

http://ruby.about.com/od/advancedruby/a/deepcopy.htm

glenn mcdonald
  • 15,290
  • 3
  • 35
  • 40