Pass by Value vs. Pass by Reference in Ruby

Recently my world was turned upside down by something that happened while iterating over an array with the each method. It went something like this:

1
2
3
["cats", "dogs", "squirrels"].each do |word|
  word << "!"
end

I expected that the return value of this block to be this:

1
["cats", "dogs", "squirrels"]

But instead, it was this:

1
["cats!", "dogs!", "squirrels!"]

Being a beginner still in many ways, I’m awarded moments like this where I can learn something new about something I before took for granted. I’m used to iterating over an array with each and having the return value be the original array, because I assumed that the each method never altered the array:

1
2
3
4
[1,2,3].each do |num|
  num += 1
end
# => [1, 2, 3] 

That’s not always the case.

Why? Objects in programming languages, in this context Ruby (it may be different in other languages), are either passed by value or passed by reference. Primitive data like integers, floats, fixnums, and symbols require a fixed, small amount of memory, so therefore are passed by their value. Objects that can grow and change, like arrays and strings, are never a fixed size. They are instead always referenced by some pointer, in order to save memory use in a program.

Thus, certain objects like strings and arrays are mutable within an each method:

1
2
3
4
[[1], [2], [3]].each do |array|
  array << 1
end
# => [[1, 1], [2, 1], [3, 1]]

Here’s another, albeit not super elegant, illustration of the (im)mutability of objects that are passed by value vs. passed by reference:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
array= [1]
string = "1"
number = 1

def push(object)
  object << 1
end

# objects that are passed by reference are changed:

push(array)
# => [1,1]
array
# => [1,1]

push(string)
# => "1\u0001"
string
# => "1\u0001"

# objects that are passed by value are not changed:
push(number)
# => 2
number
# => 1

The distinction between pass by value and pass by reference is important. It helps determine whether or not your method modifies certain types of data passed into that method that are also used by the rest of a program. If your data can be passed by reference, it’s worth it to be mindful of a method’s affect on it.

Comments