DEV Community

Cover image for Pass by Reference, Pass by Value
Jeremy Schuurmans
Jeremy Schuurmans

Posted on

Pass by Reference, Pass by Value

The other day while I was grocery shopping, someone noticed my RubyConf t-shirt and asked, "So do you actually do anything in Ruby, or did you just go to the conference?" I said, "Well, right now I'm just an amateur, but I'm hoping to be good enough to get paid for it one day." Immediately he asked, "What's the difference between pass by reference and pass by value?"

I didn't know.

I mean, I could vaguely remember having heard those terms before, but there was no way I could articulate exactly what they meant. So I went home and set about learning it, and now I can gratefully say that I not only have an understanding of an aspect of programming languages that I didn't before, but I get to pass it along to others who are maybe in the same place I was.

But before we get there, it's important to understand a couple things first: computer memory, and variables.

Memory

Analogically, I like to think of memory as a bunch of tiny labeled boxes inside my computer. When I'm writing a program in Ruby, and I create an object, that object is located in a specific location in memory -- inside one of those labeled boxes. When I need that particular piece of data, it can be retrieved from that location in memory. What is important to understand right now is that different objects will take up more or less memory depending on what they are. An integer takes up less memory space than a string.

Note: This is a purposely oversimplified analogy. Memory, and the way Ruby utilizes it, is a much more complicated topic that I hope to cover in detail at a later date (read: once I feel like I understand it enough to explain it). But if at this point your mind is thinking about heaps and garbage collection, you should find something more advanced to read. If you don't know what those terms mean, don't stress about it right now and read on, my friend. You know what you need to know at this point.

Variables and variable assignment

When we talk about "value", we're talking about something that can be assigned to or stored in a variable, which is a descriptive phrase that points to the value assigned to it. In Ruby, a value can be a string, a boolean, an integer, a float, an array, a hash ... you get the idea. Assigning objects to variables allows us to use those objects in the methods we write. That's called "passing" the variable.

If I create a variable named

best_sandwich_ever

and (controversially) assign it the string object "Reuben",

best_sandwich_ever = "Reuben"

Reuben is the value I'm working with, and my program now understands that when I use the variable best_sandwich_ever, it needs to access the string object Reuben that is stored in my computer's physical memory. As soon as I assign my Reuben string to the variable, my operating system will store the string in a specific, numbered location in memory, so now when I use the best_sandwich_ever variable, I'm using my Reuben string.

best_sandwich_ever = "Reuben"
puts "I love me a #{best_sandwich_ever}!"
"I love me a Reuben!"
Enter fullscreen mode Exit fullscreen mode

If I want to create a method that returns the best_sandwich_ever variable, I will need to define the variable within that method, otherwise it will be outside the method's scope, and the method won't have access to it.

best_sandwich_ever = "Reuben"

def fav_sammy
  return "I love me a #{best_sandwich_ever}!"
end

fav_sammy

# => NameError (undefined local variable or method `best_sandwich_ever' for main:Object)
Enter fullscreen mode Exit fullscreen mode

However, I could pass the variable to the method as an argument.

best_sandwich_ever = "Reuben"

def fav(sammy)
  return "I love me a #{sammy}!"
end

fav(best_sandwich_ever)
# => I love me a Reuben!
Enter fullscreen mode Exit fullscreen mode

Pass by reference

So what exactly is happening here? When I pass best_sandwich_ever to the #fav method, I'm passing by reference, meaning that my variable is pointing to the exact location of my Reuben in memory. The variable is pointing to the number of the box that currently contains Reuben. Why? Because my string could change over time, and thus its memory requirements would change. What if I now want to specify that the best sandwich ever is a Reuben from my favorite pub?

best_sandwich_ever << " (but only from The Old Spot)"

fav(best_sandwich_ever)
# => I love me a Reuben (but only from The Old Spot)!
Enter fullscreen mode Exit fullscreen mode

That string now requires more memory than just the string "Reuben", so to save memory and processing time, Ruby modifies the string located at the original place in memory, rather than making a copy (which we'll get to as soon as we start talking about pass by value).

The takeaway here is that it is more memory efficient to modify data in its original location in memory, than to make a copy and modify the copy, unless the data we're referring to has low memory requirements, like integers. Accordingly, in Ruby, data types that have larger memory requirements (like strings and arrays) are passed by reference.

Pass by value

Similar to the my last example, if I create a variable named

albert_einsteins_iq

and assign it the integer object 160,

albert_einsteins_iq = 160

my computer's operating system immediately places that object at a specific, numbered location in memory. At this point, if I do something crazy like sum 160

albert_einsteins_iq = 160
 # => 160 
albert_einsteins_iq.digits.sum
 # => 7 
Enter fullscreen mode Exit fullscreen mode

it might seem like I lowered albert_einsteins_iq from 160 to 7 (ouch). However, that's not the case, because as soon as I called methods on albert_einsteins_iq, a copy of the data item 160 was made and assigned to a different location in memory than the original 160, and when I summed the digits the copy was altered, leaving the original alone. We can prove this by calling the variable again and seeing what it returns.

albert_einsteins_iq
# => 160
Enter fullscreen mode Exit fullscreen mode

In Ruby, strings, arrays, and hashes are passed by reference.
Integers, floats, and booleans are passed by value.

Note: prior to Ruby 2.4, fixnum is also a data type that is passed by value, but since 2.4, fixnum and bignum became integer objects. Be aware also that pass by reference and pass by value will work a bit differently depending on which language we're talking about, but the basic concept is (mostly) the same.

So how would I have answered my grocery store friend if I had known this before? I would have said that pass by reference is when the actual memory address of the data item is passed, and when modified, the data item itself is modified. And pass by value is when the value of the data item is passed, and when modified, modifies the value leaving the data item at the original memory address unmodified.

If I pass an array containing my grocery list, what's being passed is the memory address of my array, and that array gets modified as I remove items from the list.

If I pass the number 1, what's being passed is the number 1, but at a separate memory address as the number 1 I originally assigned to a variable, and that is what is modified if I do arithmetic operations with it.

And then I would have asked him if he knew what aisle the LaCroix water was on.

Latest comments (1)

Collapse
 
sizief profile image
Ali Deishidi

Hi Jeremy, nice post, thanks for sharing! I had challenges with this concept in ruby in that past, these two things help me to understand it better:
this discussion on SO: stackoverflow.com/questions/187211...

Ans also paying attention to new object creation when we add something to passed array VS we define new array inside the method. See the code:

def append(array)
  array = array + [1]
  # array << 1 # VS this one which create new array
  p array
  p 'after assign: '+array.object_id.to_s
end

array = [0]
p 'original: ' + array.object_id.to_s
append(array)
p 'after def: ' + array.object_id.to_s
p array