parametros-por-valor-o-referencia

Passing parameters by value or reference

  • 11 min

Let’s continue talking about value types and reference types by seeing how another part of the program (a function) behaves when we pass it a variable.

If you haven’t done it, it’s highly recommended that you check out the two previous articles What is a Reference and Value Types and Reference Types (otherwise this will be tough for you).

Alright, with homework done, let’s start from the top! What interests us is, above all, knowing whether a function that receives a variable can modify it, or not (and what modifications it can make).

That’s what this whole mess is about, mainly. Let’s see the classic answer:

If the function receives a value type parameter, it cannot modify its value

If it receives it by reference, it can modify its value

So that easy, right? We’re done, go home! Eeeh… obviously not… (precisely that’s the normal answer, which I want to avoid, because it doesn’t clear up any doubt).

The real answer is:

A function always receives a copy of the parameters

“But they told me that if it’s a reference type and I pass it by reference I don’t know… and… there’s a lunar eclipse 🌒 … and… and…”. Well, what can I say, they lied to you 😜.

Therefore, the function that receives a parameter can never modify it, because it only has a copy. This has been the case since practically the beginning, and it’s inherent to how our current processor architecture works.

But of course, if your functions can’t modify anything… it takes away a lot of the fun. Well, what we can do is one thing:

  1. We pass a copy of the data to the function and it modifies its copy
  2. It passes me another copy with the result
  3. I discard the data I had and replace it with the new copy

But this would be so slow! I’d have to be moving data back and forth all the time. This won’t work, it wasn’t practical at all.

The very intelligent people who designed the processors already knew this. They had to invent a way for functions, which can only receive copies of data, to exchange data without having to copy all the entire data.

So let’s see what they invented to fix it, which is logically related to why value types and, especially, reference types exist.

How do we make a function able to modify a variable

Imagine you live in a shared apartment, with a roommate. To make it difficult, this roommate doesn’t speak your language, and works at night so you can never see them in person. You communicate through photos on your phone.

You need your roommate to sign the rental contract. But you can’t pass them the contract over the phone, you can only send them a photograph of the contract.

paso-por-valor-referencia-1

You can try sending them the photograph over and over, but obviously your roommate cannot sign it. At most they could print it and sign THEIR copy of the contract. But not the real contract.

So you think of something different. I’m going to leave the contract in a drawer, and send them a photo of the drawer where I’ve kept it.

paso-por-valor-referencia-2

This way your roommate goes at night, takes the contract, signs it. And the next day, you retrieve the already signed contract. Everyone happy 😊.

That is, instead of sending them your data, you send them a reference saying where the data is stored. This way both of you can modify the same data.

Passing parameters to functions

Let’s recap. We just saw that REFERENCES allow different parts of the program to modify a variable. This is because they don’t contain data, but links to the data.

On the other hand, functions always receive copies of the data, not the data itself. But the behavior will be different if what they receive is a value type or a reference type. (in particular, the receiving function will not be able, or will be able, to modify the parameters it receives in one case or the other).

To improve everyone’s day, many languages allow passing variables to a function by type, or passing them by reference. Which complicates things a bit more (we’ll see it right away).

So, in the end, we have four possible combinations between “passing by value, or by reference” and passing a “value type, or reference type”.

Let’s look at each of the four cases, and what can be modified or not in each of them.

  • Pass by value, value type: If the parameter is modified inside the function, it is NOT ❌ modified for the rest of the program
  • Pass by value, reference type: If the parameter is modified inside the function, YES ✔️ it is modified for the rest of the program
  • Pass by reference, value type: It is identical to the previous one, YES ✔️ it is modified.
  • Pass by reference, reference type: The function YES ✔️ can modify the parameter it receives. Moreover, YES ✔️ it can even return me a different reference.

And if we put it in a summary table it looks like this:

TypeBy ValueBy Reference
Value❌/❌✔️/❌
Reference✔️/❌✔️/✔️

Where, in each cell, the combination🅰️/🅱️ represents:

  • 🅰️: Can the function modify the variable?
  • 🅱️: Can the function return me a different variable?

Internal Functioning Advanced

If you want to understand the behavior of each of the four combinations, keep reading as we are going to analyze them.

It’s important that you understand two things well:

  • That the function always receives a copy of the parameters
  • How a REFERENCE works

And with that ahead, let’s get into it 👇

1. Parameters by value

First scenario, passing parameters by value. This is the “normal” way to pass parameters to a function.

Case 1, let’s pass a value type variable. For the example I’ll use an integer, a simple int. But any value type would work.

paso-por-valor-referencia-3

// function that changes a variable
function change_variable(int received_variable)
{
	received_variable = 20;
}

int my_variable = 10;  // create a value type variable
change_variable(my_variable)  // we pass the variable by value

// what is my_variable worth now? 10
Copied!

Let’s see what happened:

  1. We create a variable A that is worth 10
  2. The function change_variable receives a copy A-2, which has its own 10
  3. Inside the function, A-2 changes its value to 20
  4. When the function ends, A outside is still worth 10

That is, each variable has its own value. Both variables are independent, and changing one does not modify the other.

Case 2, now we do the same but with a reference type variable. For example, let’s use an array for the example, but we could use any other reference type.

paso-por-valor-referencia-4

// function that changes a variable
function change_variable(int[3] received_variable)
{
	received_variable[0] = 20;
}

int[] my_variable = {10, 0, 0};  // create a reference type variable
change_variable(my_variable)  // we pass the variable by reference

// what is my_variable[0] worth now? 20
Copied!

Let’s see what happened:

  1. We create a reference type variable A
  2. The function change_variable receives a copy of the reference A-2
  3. Both references point to the same data
  4. Inside the function, we change the value of the Reference
  5. When the function ends, my_variable has been modified

That is, now both variables change the data because they are two equal References. They are copies, they are two different references, but they point to the same data.

Therefore, if we access the data through one or the other, they are modifying the same data. That’s why the variable changes.

2. Passing parameters by reference

Passing by reference is a “syntactic sugar” to tell the program to create a reference automatically for us. That is, it’s a way the language offers to do something simpler, or easier to read.

In this case, what it makes “easier” is creating a reference, without us having to do it, or see it. Which has advantages and disadvantages (see below Tips)

Case 3, basically, is exactly the same as case 2, passing a reference by value type. Simply, instead of working with a reference, the language creates one for us.

paso-por-valor-referencia-5

We don’t even get to see this reference (and that’s why I named it ...) but it’s the same case as the previous one. That is, the function can modify the value, and it is modified for the rest of the program.

Case 4, which is the one that generates the most confusion, passing a reference type by reference. Spoiler from the Tips below, avoid doing this.

But let’s analyze it, first the most basic. If the function that receives the parameter modifies it, it modifies it for the rest of the program. Same as the other two cases (2 and 3) that involve references, only the value type / by value (case 1) escapes.

paso-por-valor-referencia-6

So what’s different about this case 4? It’s the only one that can modify the reference we pass to it so that it points somewhere else.

paso-por-valor-referencia-7

The two “automatic” references point to the same reference. That is, we can change where A points, for example to point to new data.

// function that changes a variable
function change_variable(ref int[3] received_variable)
{
	received_variable = {20, 0, 0}; // we change where the reference points
}

int[] my_variable = {10, 0, 0};  // create a reference type variable
change_variable(my_variable)  // we pass the variable by reference

// what is my_variable worth now? {20, 0, 0}
Copied!

The rest of the cases (1 to 3) we’ve seen cannot change my reference to point to something different. Cases 2 and 3 can modify the data my reference points to, but not the reference itself.

Best Practices Tips

Whenever possible, try to always avoid passing parameters by reference. It’s very atypical, and leads to weird effects that are hard to detect.

It’s much better that, if you need to pass something by reference, you explicitly create a type that wraps what you want.

For example, if you have an object football_player and you need a function that modifies them, you simply pass the player by value.

function doSomething(football_player player)
{
}
Copied!

If now you need a function that manipulates the football_players you create the object you need, for example football_team

class football_team {
   football_player[] players;
}

function doSomething(football_team team)
{
}
Copied!

There is no need to pass parameters by reference if your objects are well designed. In very rare occasions (never) is it necessary to pass parameters by reference.

On the other hand, besides the considerations we’ve seen about whether it’s possible to modify a variable from a function or not, copying by reference also has implications regarding speed.

In principle:

  • Value type variables are slightly faster to access
  • Reference type variables are much faster to copy

However, don’t give excessive importance to this, at least at the beginning. Even, many times the compiler will do things for us, like changing from value type to reference or vice versa if it estimates it will be more efficient for execution.

You simply use the type that really fits, except in cases where efficiency is really very important. And in these cases, test it well, because sometimes you get surprises like your “improvement” actually worsening performance.