Skip to main content
Ben Nadel at cf.Objective() 2012 (Minneapolis, MN) with: Nathan DeJong
Ben Nadel at cf.Objective() 2012 (Minneapolis, MN) with: Nathan DeJong

Function Results Are Returned By Value When Using CachedWithin In Lucee 5.3.2.77

By
Published in Comments (2)

A couple of weeks ago, I demonstrated that cachedWithin Function memoization compares arguments by value in Lucee 5.3.2.77. This behavior even includes complex objects like Arrays, Structs, and ColdFusion Components. Upon further testing, it also seems that the return value of a cachedWithin Function is passed by-value (not by-reference), even if the returned value is a complex object (like an Array, Struct, or ColdFusion Component).

To see this in action, all we have to do is returned a Struct from a cachedWithin Function and look at how mutations are persisted across multiple invocations of the caching Function:

<cfscript>
	
	/**
	* I return a cached-struct based on unique arguments.
	*/
	public any function getStruct() cachedWithin = "request" {

		return({});

	}

	// Get two struct using the same inputs - both of these calls will return the "same
	// result" since the cachedWithin function was called with the same inputs.
	a = getStruct();
	b = getStruct();

	// Modify both return values to see if we're modifying the same reference. If we are
	// modifying the same reference, both "valueA" and "valueB" will show in both "a" and
	// "b" in the output below.
	a.valueA = "one";
	b.valueB = "second";

	dump( label = "Result A", var = a );
	echo( "<br />" );
	dump( label = "Result B", var = b );

</cfscript>

As you can see, I am making two calls to the getStruct() Function with the same arguments (no arguments). And, since the getStruct() Function uses the cachedWithin="request" configuration, both of these calls should return the same value. Now, when we run this Lucee CFML code, modify both returned Structs, and then dump them out to the browser, we get the following output:

Cached results from a cachedWithin Function return results by-value, leading to non-persisted mutations on complex objects in Lucee 5.3.2.77

As you can see the mutation made to one of the returned values was not present in the other return value, despite the fact that they are both Struct types. This indicates that the cachedWithin feature returns values by value, not by reference. So, even though the complex object type Struct was "cached", the returned value wasn't a direct reference to said cached object.

Based on these findings, and on my previous findings, we can see that all input and output values pertaining to a cachedWithin Function appear to be value-based, not reference-based, in Lucee 5.3.2.77. In a way, this is a bit surprising; but, in another way, this makes the caching easier to reason about because you'll never run into unexpected side-effects.

Epilogue On cachedWithin Functions And Closures

As we've seen in the past with Closures and CFThread tag attributes, ColdFusion Closures tend to bend the rules when it comes to "deep copy" functionality. The same appears to be true with the cachedWithin Function feature of Lucee CFML. Take a look at this code:

<cfscript>

	/**
	* I return a Counter that will increment a shared value.
	*/
	public any function getCounter() cachedWithin = "request" {

		var id = 0;

		return(
			() => {

				return( ++id );

			}
		);

	}

	echo( getCounter()() );
	echo( ", " );
	echo( getCounter()() );
	echo( ", " );
	echo( getCounter()() );
	echo( ", " );
	echo( getCounter()() );
	echo( ", " );
	echo( getCounter()() );

</cfscript>

This time, instead of returning a Struct from the cachedWithin Function, we're returning a ColdFusion Closure that increments a closed-over value. We then retrieve the cached Closure several times and try to invoke it. And, when we do this, we get the following output:

1, 2, 3, 4, 5

As you can see, each invocation of the cachedWithin Closure acted upon the same closed-over value, id. So, while the cachedWithin functionality returns results by-value, it seems that a cached Closure will still act upon a shared reference.

Want to use code from this post? Check out the license.

Reader Comments

449 Comments

Actually. I kind of like this behavior. It gives me a way to mutate objects that are normally passed by reference. Sometimes, this can be tricky, even though we have native methods like:

Duplicate()
15,902 Comments

@Charles,

Yeah, I agree this is nice since it proactively stops you from "corrupting" the cache (depending on how you look at it). It just wasn't necessarily obvious to me before I tested it.

I believe in love. I believe in compassion. I believe in human rights. I believe that we can afford to give more of these gifts to the world around us because it costs us nothing to be decent and kind and understanding. And, I want you to know that when you land on this site, you are accepted for who you are, no matter how you identify, what truths you live, or whatever kind of goofy shit makes you feel alive! Rock on with your bad self!
Ben Nadel