Passing by reference - modifying instance variables

Hi all,

I’m new to Ruby and I’ve been working through the book “Programming
Ruby: The Pragmatic Programmer’s Guide”

Working through their jukebox example, I have come across a couple of
things that don’t quite make sense to me.

First, if I have a class with accessor methods but no mutator methods, I
can still modify the instance variables. The code in the attachment
reference.rb has the output:

Blur - Song 2 (180)
Blur - song 2 (180)

The instance variable title of song2 has been modified. This makes sense
to me, since the variables are passed by reference - I guess I’d need to
return a copy from my accessor method to avoid this happening. Is there
a neat way to do this?

To add further confusion, the code in the attachment reference2.rb -
with an identical definition of the class Song, with a call to
.downcase! in WordIndex::index does not modify the instance variables in
the Song class, even though, to my eyes, the title and artist strings
are being passed around just the same as in the previous simpler
example.

On Wed, May 16, 2012 at 4:51 PM, John C. [email protected]
wrote:

reference.rb has the output:

Blur - Song 2 (180)
Blur - song 2 (180)

The instance variable title of song2 has been modified. This makes sense
to me, since the variables are passed by reference - I guess I’d need to
return a copy from my accessor method to avoid this happening. Is there
a neat way to do this?

Dunno whether you find that neat

def foo
@foo.dup

ultra save: @foo.dup rescue @dup

end

You could even create a custom variant of attr_reader which generates
this.

Or you freeze instance variables.

To add further confusion, the code in the attachment reference2.rb -
with an identical definition of the class Song, with a call to
.downcase! in WordIndex::index does not modify the instance variables in
the Song class, even though, to my eyes, the title and artist strings
are being passed around just the same as in the previous simpler
example.

word.downcase! works on the scan result which is not the original
string.

irb(main):001:0> s = “FOO”
=> “FOO”
irb(main):002:0> s.scan(/…/) {|m| p m; m.downcase!; p m}
“FO”
“fo”
=> “FOO”
irb(main):003:0> s
=> “FOO”

Kind regards

robert

John C. wrote in post #1060987:

The instance variable title of song2 has been modified. This makes sense
to me, since the variables are passed by reference

Just to be clear: in ruby there is no pass by reference. Never. It is
all pass-by-value, but the value is an object reference.

This is important, because it is impossible to pass a reference to a
local variable (as you can in some other languages) and have the local
variable updated remotely. Local variables are not objects, and you
cannot refer to them as if they were.

So when you do title.downcase! you are not changing the value of title
(which still refers to the same object); you are mutating the object it
refers to.

Simple example:

a = “foo”
b = a
puts b.object_id
b.upcase!
puts b.object_id
puts a # FOO
puts b # FOO

a and b contain the same value, which is the same string object “FOO”.
b.upcase! does not change the variable b, but it changes the string
object.

I guess I’d need to
return a copy from my accessor method to avoid this happening. Is there
a neat way to do this?

def title
@title.dup
end

But this is not a common Ruby idiom, because it does not work in the
general case, for example where dup is an array, or any more complex
graph of objects. Such a “deep copy” is possible, but increasingly
inefficient the more objects are included in the graph.

A slightly more common idiom is to freeze the object, if you don’t want
the caller messing with it. But normally you would trust the caller to
behave.

To add further confusion, the code in the attachment reference2.rb -
with an identical definition of the class Song, with a call to
.downcase! in WordIndex::index does not modify the instance variables in
the Song class

phrase.scan(/whatever/) returns an array of new String objects with each
of the matches.

a = “foo”
=> “foo”

b = a.scan(/…/)
=> [“foo”]

a.object_id
=> 2201532960

b.first.object_id
=> 2201525680

So a and b.first are two distinct Strings, although they both contain
the characters “foo”