Ben Nadel
On User Experience (UX) Design, JavaScript, ColdFusion, Node.js, Life, and Love.
Ben Nadel at the ColdFusion Centaur And Bolt User Group Tour (Jun. 2009) with: Ben Forta and Alison Huselid and Adam Lehman
Ben Nadel at the ColdFusion Centaur And Bolt User Group Tour (Jun. 2009) with: Ben Forta@benforta ) , Alison Huselid , and Adam Lehman@adrocknaphobia )

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

By Ben Nadel on
Tags: ColdFusion

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.



Reader 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()
Reply to this Comment

@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.

Reply to this Comment

Post A Comment

You — Get Out Of My Dreams, Get Into My Comments
Live in the Now
Oops!
NEW: Some basic markdown formatting is now supported: bold, italic, blockquotes, lists, fenced code-blocks. Read more about markdown syntax »
Comment Etiquette: Please do not post spam. Please keep the comments on-topic. Please do not post unrelated questions or large chunks of code. And, above all, please be nice to each other - we're trying to have a good conversation here.