Function Results Are Returned By Value When Using CachedWithin In Lucee 5.3.2.77
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:
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.
cachedWithin
Functions And Closures
Epilogue On 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
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:
@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.