Lucee variable assignment is using reference instead of value

I have a function which uses the arguments.roleid variable. I am setting a new variable roleId with the value of arguments.roleid.

<cfset roleId = arguments.roleId>

Now, if I change the value of roleId, it is affecting the arguments.roleId as well. When we assign an arguments variable to another variable, is it by reference? In some other version of Lucee I can confirm it was by value only and the change of the roleId does not affects the arguments.roleId.

Lucee version: Lucee 5.3.7.48

Yes, lucee passes arguments by reference. So changes done on the argument scope will reflect in the original value.

You can change the argument to pass by value using passBy attribute in cfargument

Like

<cfargument name="roleId" passBy="value">

OR use the duplicate function

<cfset roleID = duplicate(arguments.roleId)>
4 Likes

Oh, super interesting. I had no idea that feature was in there.

2 Likes

Whilst this is true, it is not answering the question that was asked, and your answer goes on to be pretty misleading.

@Prasanth_Kumar_S you could have answered your own question very easily by instead of writing out the question describing your code, write out the code that the question asked, and run it at look at the results.

The question that was asked was not asking about the argument scope, it was asking about one of the values in the arguments scope. Those are two different things.

Given this code:

<cfset roleId = arguments.roleId>

arguments.roleId is at one location in memory, and roleId is in a different location in memory (so each has a different reference pointing to the different memory locations), so once you make the assignment, they are two different values. That the arguments scope is mentioned here is neither here nor there.

The guidance that would have been better to give here is “complex values are copied by reference, but simple values are copied by value”. roleId is clearly a simple value here (I’m presuming an int), so it’s copied by value. After that assignment, you have two copies of the value of roleId.

If your question had been “what would happen here?”:

<cfset someOtherStruct = arguments.someStruct>
<cfset someOtherStruct.newKey = "new value">

<cfoutput>#arguments.someStruct.newKey #</cfoutput> <!--- would this work or error? --->

My answer still would have been “try it and see”, but another answer about how complex values are copied would then be relevant.

Or another example:

<cfset someOtherStruct = arguments>
<cfset someOtherStruct.newKey = "new value">

<cfoutput>#arguments.newKey #</cfoutput> <!--- would this work or error? --->

This is closer to the question @cfmitrah was answering. And my answer would remain “what happened when you tried it?”.

–
Adam

ah, but what happens with localmode=true?

1 Like

Ugh (or “duh” depending on how uncharitable I am feeling about myself).

Just noticed it’s the same name on each side. So this is just a scope-look-up thing, not a pass-by-reference (or pass by anything) situation.

<cfset roleId = arguments.roleId>

Is the equiv of going

<cfset arguments.roleId = arguments.roleId>

And indeed any unscoped reference to roleId in a function that has an argument roleId is not “a new variable”, it’s just… the argument.

So

<cfset roleId = "new value">

Is the same as

<cfset arguments.roleId = "new value">

So of course it’s “it is affecting the arguments.roleId as well”. Except the “as well” is extraneous there, cos there’s nothing else in play.


The point remains, though, that if one is setting a new variable from an element of a struct (or a scope) that is a simple value, it’s a new copy of that value, and a different reference. It’s just that the initial statement in the original post does now accurately describe what the code is actually doing!

–
Adam

3 Likes

This answer is an improvement over the CFMitrah’s answer, but I’m going to improve it even more :slight_smile:

“variables” in the JVM (at least the way we think of them) involve two separate things

  • The actual allocation on the heap that stores the contents of the object (whether it’s a string, array, struct, etc)
  • The named pointer in your code that “references” the memory location.

You can have a chunk of memory in the heap with thousands of pointers referencing it, or none at all (which would render the object eligible for garbage collection).

var myVar = 'here is the value';
variables.anotherVar = myVar;
session.foo=myVar;
application.bar=session.foo;

Now, if you were to capture a heap dump and look “under the hood” at the raw memory in the JVM after running the code above, the string here is the value is only going to stored once. Passing it to a function makes no difference. It’s just another references to the same string in memory. Think of note cards pinned on a board with a string tied to the actual object they point to. The note cards are just variable names and the string is the reference to the object in memory. Each of my variable assignment statements above simply adds another card and another string tying back to the original memory location.

Therefore, it’s not quite correct to say “different memory locations” unless we’re referring to the pointers (which do ultimately live in memory as part of their containing object/scope). The arguments scope is not a different “value” per se, it’s just another pointer/refernce/string to the original value.

We should avoid the word “copied” here as it’s simply not correct. In my example above, there are no “copies” of my string anywhere. It exists once in the heap and there are multiple pointers to it. Further more, we don’t have two “copies” of the roleID, we have two pointers to the same roleID in the heap.

So what is the difference? The difference is this

  • replacing a pointer so that a variable name (note card) has its string cut and gets re-tied to a new memory allocation.
  • vs mutating the original object in place that has the variable references pointing to it

When you set one variable to another, or pass an argment to a function, you are not creating “copies” of anything. You are just tying strings between note cards. Complex values are capable of being mutated (changed) in place, and when that happens every variable (note card) that is tied (has a reference in memory) to that object now all “sees” the same object and they observe the changes. So it’s not “copied by reference” per se-- it just IS a reference to a shared object which is changing.

New java fact-- Java classes such as strings, ints, or doubles are immutable (just the way Java works). That means a Java string or double CANNOT be mutated by the design of the language. If you want to change a string, you must create a new string in memory, point to that one, and the old string will eventually be garbage collected. So simple values are not “copied by value” per se, all variables ARE pointing to the same vaue, it’s just that when you change a string, it creates a new string and ONLY the variables you specify get their reference updated. All the other variables are still pointing to the original string

myVar = 'original value';
// Stil only one string exists in memory
anotherVar = myVar;
// Now we create a new string and update this variable reference to point to it
anotherVar = 'new value';

In fact, we can simplify this. Forget about whether the values are simple or not-- the rule of thumb is when you completely replace a variable with a new value, you get a new object in memory and a new reference! It’s that simple, and it applies across the board to strings, numbers, structs, arrays, CFC, etc.

Let’s take that same example, but make it a struct:

myVar = {};
anotherVar = myVar;
// object mutated in place, no references changed
myVar.foo = 'bar';

// brand new object created and reference/pointer updated to new memory location
anotherVar = { 'baz' : 'bum' };

So, you can see it’s not about “copying”, it’s about memory references and overwriting those references vs mutation an object in place. This is true whether it’s just setting a variable, or passing it to a function.

** to explain the legacy Adobe CF behavior-- the CF engine itself would internally duplicate() any complex values before passing them to a function for… reasons. Which in that case actually created a literal copy in memory. Luce has never done this.

5 Likes

Sorry Adam, your corrections came in while I was typing :slight_smile:

1 Like

Excellent clarification of the under-the-hood-ness, thanks for that.

So like the value isn’t copied (for strings… not so sure about ints though, and pretty sure roleId is gonna be an int here? So the value of the int… eg 17 will actually exist twice in memory, yeah? I am guessing and my recollection is rusty, so could be wrong), but the value of the reference is.

arguments.myString has a value that is a reference to where the string is in the string pool, yeah? And when I go myOtherString = arguments.myString, myOtherString is a (different ~) reference, and its value is a copy of the value of the reference arguments.myString is. It’s not that myOtherString and arguments.myString are the same reference? Is it? They’re still different references that happen to have the same value. And the value is the… argh… pointer to where the string is in the string pool?

(I always lose track of the terminology with references (technical term) and reference (colloquial term) and pointer. And memory address values).

Anyway cheers. Like I said… good improvement over my woolly wording :smiley:

–
Adam

All good man: it was great information.

More interesting than the original question, too :smiley:

–
Adam

1 Like

Hi,
Thank you @cfmitrah , @AdamCameron , @Zackster , @bdw429s for the detailed explanation. I understand that the original issue was due to the fact that the ARGUMENTS scope is having priority inside a function.
The passBy in the cfargument is new for me. :slight_smile:
The detailed explanation have helped me understand that I need to check the new features in Lucee much more closely.

3 Likes