Language: EN

parametros-por-valor-o-referencia

Passing parameters by value or reference

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

In case you haven’t already, it is highly advisable that you have looked at the two previous articles What is a reference and Value types and reference types. Otherwise, this is going to be quite tough for you.

So, with the homework done, let’s begin with the most important part! What we are interested in, above all, is to know if a function that receives a variable can modify it or not, and what changes it can make.

That’s the main topic of this whole mess. Let’s see the classic answer:

  • If the function receives a value type parameter, it can’t modify its value
  • If it receives it by reference, it can modify its value

That easy, right? We are done now, let’s go home! Eeeeh … obviously not… That is just the usual answer, which doesn’t clarify anything.

The real answer is:

A function always receives a copy of the parameters

“Well, I’ve been told that if it is a reference type and passed 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 is inherent to the way the current architecture of our processors works.

But of course, your functions not being able to modify anything… takes a lot of the fun out of them. So, what we can do is:

  • We pass a copy of the data to the function and modify its copy
  • It gives me another copy with the result
  • I discard the original data and change it for the new copy

But this would be very slow! I would have to be moving data around all the time. The thing is not going to work like that, it wasn’t practical at all.

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

So, let’s see what they came up with to fix it, which is logically related to why there are value types and, above all, reference types.

How we make a function able to modify a variable

Imagine that you live in a shared apartment, with a roommate. To make it difficult, this roommate does not speak your language, and works at night so you can never see him in person. You communicate through photos by mobile.

You need your roommate to sign the lease. But you can’t send the lease to him by mobile, you can only send a photo of the lease.

paso-por-valor-referencia-1

You can try sending the photo over and over, but obviously your roommate can’t sign it. At most, he could print it and sign HIS copy of the lease. But not the real lease.

So, you come up with a different thing. I will leave the lease in a drawer, and send him a photo of the drawer where I’ve kept it.

paso-por-valor-referencia-2

So your roommate goes at night, takes the lease, signs it. And the next day, you retrieve the signed lease. Everyone is happy 😊.

In other words, instead of sending him your data, you send him a reference saying where the data is stored. So you both can modify the same data.

Passing parameters to functions

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

On the other hand, functions always receive copies of the data, not the data. But the behavior will be different if what they receive is a value type or a reference type. In particular, the function that receives may or may not be able to modify the parameters it receives, depending on the case.

To improve everyone’s day, many languages allow you to pass variables to a function by type, or pass them by reference. This further complicates things. We will see it shortly.

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

Let’s see 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 within the function NO ❌ it is not modified for the rest of the program
  • Pass by value, reference type: If the parameter is modified within the function, YES ✔️ it is modified for the rest of the program
  • Pass by reference, value type: It is identical to the previous case, YES ✔️ it is modified
  • Pass by reference, reference type: The function YES ✔️ can modify the parameter it receives. Furthermore, YES ✔️ it can even return a different reference to me.

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 a different variable?

Internal operation Advanced

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

It is important that you understand two things well:

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

So with that in mind, let’s get into this 👇

1. Parameters by value

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

1.1 - Passing by value, a value type

Case 1, let’s pass a value type variable. For the example I’m going to use an integer, a simple int. But it could be any value type.

paso-por-valor-referencia-3

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

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

// what is the value of my_variable now? 10

Let’s see what happened here:

  • We create a variable A that is worth 10
  • The function change_variable receives a copy A-2, which has its own 10
  • Inside the function, A-2 changes its value to 20
  • After the function ends, A outside still worths 10

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

1.2 - Passing by value, a reference type

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};  // I create a reference type variable
change_variable(my_variable)  // we pass the variable by reference type

// what is the value of my_variable[0] now? 20

Let’s see what happened here:

  • We create a reference type variable A
  • The function change_variable receives a copy of the reference A-2
  • Both references point to the same data
  • Inside the function, we change the value of the Reference
  • After 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 indicate to the program that it automatically creates a reference for us. That is, it is a way that the language offers to make a thing easier, or easier to read.

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

2.1 - Passing by reference, a value type

Case 3, basically, it 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 see this reference (and that’s why I named it ...) but it is 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.

2.2 - Passing by reference, a reference type

Case 4, which generates more confusion, passing a reference type by reference. Spoiler of the Tips below, avoid doing this.

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

paso-por-valor-referencia-6

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

paso-por-valor-referencia-7

The two “automatic” references point to the same reference. That is, we can change the value to 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 to
}

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

// what is the value of my_variable now? {20, 0, 0}

The other cases (1 to 3) we have seen cannot change the reference to point to something else. Cases 2 and 3 can modify the data it points to, but not the reference itself.

Tips Tips

As much as possible, try to always avoid passing parameters by reference. It is very atypical, and leads to strange effects that are difficult to detect.

It is 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 a soccer_player object and you need a function to modify them, simply pass the player by value.

function doSomething(soccer_player player)
{
}

If now you need a function that manipulates the soccer_player, create the object you need, for example soccer_team

class soccer_team {
   soccer_player[] players;
}

function doSomething(soccer_team team)
{
}

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

On the other hand, in addition to the considerations we have seen about whether it is possible or not to modify a variable from a function, copying by reference also has implications in terms of speed. In principle:

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

However, do not give excessive importance to this, at least at the beginning. Even, many times the compiler will do things for us, such as changing from value type to reference type or vice versa if it deems that the execution will be more efficient.

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