ColdFusion and Pass by Reference versus Value

This post is more than 2 years old.

So this is something I've blogged about before, but as it recently burned a friend of mine, and got some pushback from folks on Twitter, I thought I'd raise the issue again.

When working with ColdFusion, any complex data type (structs, CFCs, queries, COM/JavaObjects, did I miss one?) will be passed by reference, not by value.

For those of you who took English Lit 1 instead of Comp Sci 101, all this means is that these variables are not copied when sent to a function. In other words, when I do:

<cfset newThing = changeIt(oldThing)>

Under normal circumstances, if changeIt manipulates values in oldThing and does a return on it, my newThing will have the manipulated values and oldThing will be just fine.

But with complex data, like structures, you are actually passing the exact same object (a reference). That means any changes inside the UDF will be reflected on the original object.

As an example:

<cfscript> function fubar(data) { arguments.data.name = "Britney"; return arguments.data; } </cfscript>

<cfset s = {}> <cfset s.name = "Paris"> <cfset s2 = fubar(s)> <cfdump var="#s#" label="S"> <cfdump var="#s2#" label="S2">

I've created a simple structure with one key. I've created a UDF that changes the value and returns a 'new' struct (right?). But look at what the dumps show:

Fail! Luckily enough it is easy to fix:

<cfset s2 = fubar(duplicate(s))>

The duplicate function will create a deep copy of the structure before sending it to the UDF.

Now, here is where things get interesting. Did you notice I didn't mention arrays in my list of complex objects? Well, they are indeed copied by value, not by reference, but, if you include a structure as part of the array, the structure portion will be copied by reference. Check this out:

<cfscript> function fubar2(data) { arguments.data[1] = 9; arguments.data[2].gender = "female"; arguments.data[3] = "Not so goofy"; return arguments.data; } </cfscript>

<cfset a = []> <cfset a[1] = "42"> <cfset a[2] = {}> <cfset a[2].gender = "male"> <cfset a[3] = "Goofy"> <cfset b = fubar2(a)> <cfdump var="#a#" label="A"> <cfdump var="#b#" label="B">

I've got an array with 3 values. Values 1 and 3 are strings, value 2 is a structure. My new UDF, fubar2, changes all 3 values. And the result?

As you can see, the simple values weren't changed in the original, but the embedded structure was. As before, using a duplicate around 'a' would solve the problem.

But is it a problem? Well certainly if you aren't expecting it, but the fact that structs and other complex objects pass by reference isn't "wrong" per se, and it can be useful as well. Just don't forget it.

Raymond Camden's Picture

About Raymond Camden

Raymond is a senior developer evangelist for Adobe. He focuses on document services, JavaScript, and enterprise cat demos. If you like this article, please consider visiting my Amazon Wishlist or donating via PayPal to show your support. You can even buy me a coffee!

Lafayette, LA https://www.raymondcamden.com

Archived Comments

Comment 1 by Rolando Lopez posted on 5/1/2009 at 7:23 PM

I guess you missed arrays :)

Comment 2 by Raymond Camden posted on 5/1/2009 at 7:24 PM

Eh?

Comment 3 by Justin Carter posted on 5/1/2009 at 8:05 PM

You asked if you missed any complex data types :) An array is, although in CF arrays are passed by value not by reference.

Comment 4 by Andrea posted on 5/1/2009 at 8:08 PM

I find passing ararys by value as something that shoudl be solved. Adobe could think to a checkbox in admin for keeping back compatibility but passing arrays by value looks like a bug to be solved and not like a language tipical aspect to be preserved.

Comment 5 by Andy Sandefer posted on 5/1/2009 at 8:33 PM

This is just a buyer beware type of thing and every language has its own little gotchas. This is important to know but I'm not sure that changing it after 8 releases would be a good thing. Just my opinion as someone who has had legacy code broken for the sake of satisfying someone's newly found ideology. I'm sure that there are probably strong arguments to be made either way.

Comment 6 by Andrea posted on 5/1/2009 at 8:41 PM

@Andy
I do nto think is only ideology.
Passing by value means that any time you pass an array a new one is made.
This is one thing but cf is full of these littles tricks that make it run slower than railo for example..

Comment 7 by Raymond Camden posted on 5/1/2009 at 9:55 PM

@Justin: Oh, well, I thought Rolando meant I missed talking about arrays, and that was my second area. Yes, they are complex, I just wanted to make sure to separate them from the 'expected' list of things passed by ref.

Yes, I'm being defensive. Sue me. ;)

Comment 8 by phill.nacelli posted on 5/1/2009 at 10:51 PM

Ray and I had this discussion while he was writing this blog about whether or not this should be fixed in future versions, it's valid that backward compatibility issue would be at play here but this is a inconsistency nonetheless. I am glad however that he posted this gotcha so at least people are aware of the implications and won't go crazy if they see this odd behavior.

cheers..

Comment 9 by Justin Carter posted on 5/2/2009 at 4:21 PM

@Ray: Yeah of course, the most important part of your article is the distinction that arrays aren't passed by reference like other complex types and need to be thought of separately :)

Comment 10 by Jim_Collins posted on 5/3/2009 at 8:07 AM

Ray; I love you man but your example isn't very good; it's what I'd expect. This gives you the full "WTF?!?!" effect:

<cfset s = {} />
<cfset s.name = "Britney">
<cfset s2 = s />
<cfdump var="#s#" label="S">
<cfdump var="#s2#" label="S2">
<cfset s2.name = "Paris">
<cfdump var="#s#" label="S">
<cfdump var="#s2#" label="S2">

Result: All Paris all the time!

Comment 11 by Chuck Savage posted on 12/7/2009 at 10:26 PM

Ben Nadel posted a 'sweet' coldfusion pass array by reference, that has a caveat perhaps...

http://www.bennadel.com/blo...

Comment 12 by Joel posted on 3/3/2010 at 12:59 AM

Passing arrays by value = FAIL

Comment 13 by Drew Wells posted on 6/15/2010 at 12:37 AM

I agree above, arrays are passed by value incorrectly. Took me a while to figure out that Adobe dropped the ball here.

Comment 14 by mike cohen posted on 2/11/2011 at 10:44 PM

Its interesting to me that structs are passed by reference. C passes structs as value but I guess since Coldfusion is Java structs is just another object and so passed by reference.

Comment 15 by James Hull posted on 11/28/2011 at 4:13 PM

"When working with ColdFusion, any complex data type (structs, CFCs, queries, COM/JavaObjects, did I miss one?) will be passed by reference, not by value."

This is technically incorrect. The reference to the object is actually passed by value. Most of the time you don't have to worry about it, but imagine you had something like this:

public void function test() {
var s = {
"name" = "James"
};

refTest(s);

writeDump(var:s, abort:true);
}

private void function refTest(struct t) {
t = {};
}

You might assume that it will dump out an empty struct when it actually dumps out the unmodified original struct. This is because the value of s (the reference to the struct) is passing a copy to refTest.

Comment 16 by Raymond Camden posted on 11/28/2011 at 4:35 PM

Interesting - yet my other examples do show this behavior. So am I wrong - or just not specifying this right?

Comment 17 by James Hull posted on 11/28/2011 at 4:41 PM

The other examples work because the reference points to the same object.

So in my example above - refTest(struct t) - t contains a copy of the reference to struct s. If I was to do:

t.name = "Ray";

The dump would now show

name = "Ray"

This is, as you state in your post, because refTest used the reference to alter the underlining struct. However, if (like I did originally) change t to point to a new struct hence a new reference - this doesn't affect the original reference in my caller function. s still points to the same struct. This is because the reference was passed by value. In other words a copy of the reference was created and passed to the refTest function.

Comment 18 by Raymond Camden posted on 11/28/2011 at 4:48 PM

Interesting. But wouldn't you say - in a typical case - when you pass a struct to a method the method is normally going to do something with it. Like add or remove a key? And in most cases, the issue of it changing the original would possibly hurt people?

Comment 19 by James Hull posted on 11/28/2011 at 4:54 PM

Yeah sure. For the most part - I don't imagine most people would run into this. I was just pointing out that whilst in general most people would think of passing a struct is passing by reference - technically it's not.

A colleague of mine actually come up against this very issue a few months ago. He was passing a structure into a function which used the values contained in the struct and then he wanted to "clear" it by creating a new structure. Bit of a shock when in the caller function - the structure still contained all the original values!

Comment 20 by Raymond Camden posted on 11/28/2011 at 5:01 PM

Maybe I'm being picky - but how is it _not_ passing by reference? It seems like you are saying that when you made T a new struct, the reference broke. But it was still a reference at first. So it was -passed- in as a reference, but then the connection was lost. Right?

Comment 21 by James Hull posted on 11/28/2011 at 5:10 PM

The formal definition of pass by reference is actually quite complex but the important thing to note is that if you pass a variable by reference, the method you're calling can change the value of the caller's variable by changing its parameter value.

As you see from my example - if I change the parameter value (t) it doesn't change the callers variable (s). In other words, if I change t to a new reference - s still stays the same. So by it's formal definition - this is not pass by reference.

From a abstract view - you can think of this as passing by reference - because the argument to refTest is a reference. But the point I was trying to make is by the correct definition of pass by reference, passing a struct or any complex type is still pass by value - we just happen to be passing a reference.

It's probably me just being pedantic, however I think it's a distinction that people need to be aware of.

Comment 22 by Raymond Camden posted on 11/28/2011 at 5:12 PM

I concede your point - I just think it's going to _act_ more like the first case then the second. But I see your point.