A reader pinged me last night with something he was convinced was a bug, but had said that Adobe shot him down and told him it was expected behavior. The behavior in question involved arraySet, a function I rarely use myself, but is useful for folks who want to quickly initialize multiple values in an array.
What he noticed was that when he used arraySet with structs, each array element pointed to the same structure. Here is an example:
<cfset a = []> <cfset arraySet(a,1, 5, {})>
<cfset a[1].name = "ray"> <cfdump var="#a#">
The result of this is:
As you can see, I only modified the first array element, but all five were modified. We get this result because the structure created within the arraySet function is copied by to all the array elements, but copied by reference. This is expected (although it trips people up) for structures and other complex data. It is also expected, or should be, that if you use a function for the value in arraySet that it will only be run once. So for example:
<cfset arraySet(a,1, 5, createUUID())>
<cfdump var="#a#">
When run this returns:
Again, I don't think any of this is wrong, or even unexpected, but I certainly can see forgetting this myself.
Archived Comments
There's a similar gotcha with udf's & loops too, as this example shows - http://pastebin.com/WbYMRUwM
(FYI, for future reference, I think a code sample like that is short enough to be in the comment. Up to you though. :)
You could fix that by changing:
#event.linkto("some.event", {id=q.id})#
to
#event.linkto("some.event", {id=q.id[currentRow]})#
but I can see how one would expect the original version to work.
@Chris, that is a curious bug. At least it seems like a bug to me. It must have something to do with how CF deals with structure literals withing functions -- that's something that was added in CF9. My first thought was, you should be able to do this:
#event.linkto("some.event", {id=id})#
But that produces an error "Variable ID is undefined". So I thought, wait a minute, if that produces an error, can you really do what Ray says and use currentrow? In fact you can't. In my test I got "Variable CURRENTROW is undefined".
What?? I call BS. :) But I didn't run it. Doing so now.
:-) I would have called BS too. Here is my test code:
<cfoutput query="q">
#test({id=q.id[currentrow]})#
</cfoutput>
<cffunction name=test output="true">
<cfargument name="s" type="struct">
#s.id#
</cffunction>
Wow, I stand corrected! Given this code:
<cfquery name="art" datasource="cfartgallery">
select artid, artname
from art
</cfquery>
<cfscript>
function foo(struct s) {
return serializeJSON(s);
}
</cfscript>
<cfdump var="#art#" top=3>
<cfoutput query="art">
<cfset x = artid[currentrow]>
<!---calling foo with #artid# = #foo({id=art.artid[currentrow]})#<br/>--->
<!--- calling foo with #artid# = #foo({id=x})#<br/> --->
<cfset y = {id=artid}>
calling foo with #artid# = #foo(y)#<br/>
</cfoutput>
The two items commented out both failed. I'm going to file bug reports on this guys - and I apologize for assuming.
Bug filed.
Thanks, Ray. This is disappointing because I was really looking forward to using this kind of code freely (I still have to target CF8 for my current projects.)
One additional note: this doesn't produce an error:
test({id=q.id[q.currentrow]})
but it has the same problem as test({id=q.id}). I guess the structure is being created outside of the cfoutput scope.