Rest Operator Doesn't Work With Only One Argument In Adobe ColdFusion
In Adobe ColdFusion, as in many languages, you can use the rest operator to collect an open-ended number of function arguments into an array. But, I ran into a bug wherein the Rest operator doesn't work when only a single "rest argument" is passed-in during function invocation. When a function is invoked with a single argument, and the function is only parameterized using the rest operator, the value comes through as is, and is not collected into an array.
To see what I mean, let's dump-out the core Java type of the "captured value" in the rest argument when a various number of arguments are used during invocation:
<cfscript>
inspectRestType( "this", "is", "Sparta!" );
inspectRestType( "hello", "world" );
inspectRestType( "skadoosh!" );
/**
* I echo the core data type of the VALUES rest collection.
*/
public void function inspectRestType( ...values ) {
writeOutput( values.getClass().getName() );
writeOutput( "<br />" );
}
</cfscript>
Notice that the inspectRestType()
function signature has only a single parameter—the rest collection. Now, when we run this in Adobe ColdFusion and see how the rest operator collects the various arguments, we get the following output:
java.util.ArrayList
java.util.ArrayList
java.lang.String
When 2+ arguments are used during invocation, the values are correctly collected into a ColdFusion Array (which is an ArrayList
under the hood). But, when only a single argument is used during invocation, it's passed-through as is. In this case, it comes through as the String, "skadoosh!"
.
The CFFiddle.org site is locked down; so, I can't read the underlying Java classes. But, we can just dump-out the rest value to see if it's an array. Here's what I tried to run in the Adobe ColdFusion 2025 Beta:
<cfscript>
dumpRestCollection( "this", "is", "Sparta!" );
dumpRestCollection( "hello", "world" );
dumpRestCollection( "skadoosh!" );
/**
* I dump-out the VALUES rest collection.
*/
public void function dumpRestCollection( ...values ) {
writeDump( values );
}
</cfscript>
If the rest operator were working correctly, we'd expect all three writeDump()
calls to output an array. However, when we run this code on all available versions of Adobe ColdFusion (including the 2025 beta), we get the following:
As you can see, the first two dump-outputs are an array. But, the third dump-output of the single-argument invocation outputs a string (the passed-in argument value).
As a final check, I wanted to see if this was a bug in functions that only took a single argument; or, if this as a bug in how the rest operator behaved when collecting only a single argument. To test, I modified the previous script to have two arguments:
<cfscript>
dumpRestCollection( "first-input", "this", "is", "Sparta!" );
dumpRestCollection( "first-input", "hello", "world" );
dumpRestCollection( "first-input", "skadoosh!" );
/**
* I dump-out the VALUES rest collection.
*/
public void function dumpRestCollection(
required any firstInput,
...values
) {
writeDump( arguments );
writeDump( values );
}
</cfscript>
Unfortunately, this demonstrates the same bug - even when the function always requires a first argument, the rest operator still fails to collection the 2..N
argument into an array if only a single optional value is passed-in:
I'll file a bug in the Adobe Bug Tracker and post it in the comments below.
Building Your Own Rest Operator Functionality
As a final note, the rest operator is just syntactic sugar on top of what we can already do in ColdFusion. If you're in a situation where you need to collect an open-ended number of arguments into an array, you can do that yourself by treating the arguments as an array-like data structure and slicing it. There are some precautions that need to be taken into account; so, isolating this logic in another function makes good sense.
Here's a refactored example that defines a collectArguments()
function that will safely slice-out the 2..N
arguments. In the following code, I've defined an argument named ___rest_of_arguments
as a way to try and clearly document that the dumpRestCollection()
function is a variadic function.
<cfscript>
dumpRestCollection( "first-input", "this", "is", "Sparta!" );
dumpRestCollection( "first-input", "hello", "world" );
dumpRestCollection( "first-input", "skadoosh!" );
dumpRestCollection( "first-input" );
/**
* I dump-out the values for the 2...N arguments.
*/
public void function dumpRestCollection(
required any firstInput,
any ___rest_of_arguments
) {
writeDump( collectArguments( arguments, 2 ) );
}
/**
* I collect a segment of the given argument collection into an array. This only works
* if the given "args" does NOT include a REST-based operation.
*/
public array function collectArguments(
required any args,
required numeric startingAt
) {
// Note: I'm using the global functions instead of the member methods since the
// argument collection is a special data structure that acts as both an array and
// a struct. As such, to get array-like behaviors, we have to use the global array
// functions that know how to deal with array-like data.
return arrayIsDefined( args, startingAt )
? arraySlice( args, startingAt )
: []
;
}
</cfscript>
The collectArguments()
function will successfully collect the 2..N
arguments into an array; and will return an empty array if the original function is only invoked with the one required argument. Though, in this particular implementation, it won't behave well if the first "rest argument" is undefined since it's using the araryIsDefined()
function to determine if any "rest arguments" exist.
Want to use code from this post? Check out the license.
Reader Comments
I've submitted a bug report: CF-4225664.
@BenNadel
Hey Ben
This is very interesting.
I actually never knew that Coldfusion had a rest operator. Does Lucee have this, as well?
And why would you use this, over the arguments struct? Maybe, if you want to guarantee the order of the arguments, which an array will do and a struct won't?
And one last question, does Coldfusion have a spread operator?
@Charles,
Yes, they have a spread operator as well. I don't think that Lucee CFML 5.x has either (I vaguely remember going to use it and it throwing a compilation error). Maybe it come in Lucee 6?
To be honest, I rarely reach for it. ColdFusion has so much parallel functionality that I haven't really found myself needing Rest and Spread all that much. Between things like the
argumentCollection
,invoke()
, and treating arguments like Struct-Arrays, we have so many of our bases covered.You can also use the Rest operator in destructuring, which is something I almost never reach for. But, fun to know that it exists. You can use the rest operator to get the tail of an array:
You can then construct new arrays with the spread operator:
Now, I know this stuff exists, but I don't find that I actually need it all that much. And, clearly (as per this post) there are cases in which it doesn't really work well.
@Ben Nadel,
This is fabulous information. 🙂
I never realised that Coldfusion had destructuring, either. I actually really like
destructuring, which I use a lot, when writing JavaScript.
It's a little surprising that Lucee doesn't contain these kinds of constructs, considering Lucee is all about modernisation.
I may check to see if Lucee 6 has:
@Charles,
Yeah, I know they added it at some point, but I don't remember which version :) I don't have much experience with destructuring - I'd be curious to hear where your common use-cases are.
@Ben Nadel,
Hmmmm. Now you've got me. 🤩
I mean I use destructuring mainly, in JavaScript, to extract key/value pairs into individual variables.
This kind of thing was used in React, a lot when using hooks, like:
As far as a use case goes, let's say you've got a huge object [struct in CF], destructuring helps to make code more readable. It clarifies which keys in an object, are the main players, as it were.
It is declarative.
I actually use it a lot when I am designing REST API applications in NodeJs.
It is great to take a massive JSON object, passed into a route handler, and explicitly tell the method what variables are required from the object.
And I can actually see this working in component methods, as well, when passing structs or arrays as arguments
Like when we used to add our var scoped variables, at the top of a method, to set out our stall, so to speak.
I generally use the local scope now.
But, we could do something like:
And the other great thing about this, is that it bypasses the problem with Coldfusion, whereby you cannot assign multiple vars, in a single declaration, like:
And I might be a good way of unpacking an argument collection like:
This could be for the purists who prefer not to have to prefix their args with the arguments scope, which although isn't necessary, is something I feel I should do, to make things clear to other Devs, in the team.
Doing it, the above way, makes it clear to other Devs, whilst still scoping the variables correctly.
But, others might think this is overkill?
Here is quite a nice article on Coldfusion destructuring
https://www.bockensm.com/2022/10/11/scoping-destructured-variables/
@Charles,
Ah yes, I see this pattern in the
use*
things in React a lot. I don't know very much about that part of React; but, I've seen enough code to know this is the common pattern. I know this extremely subjective; but, the fact that React uses destructuring so often always makes me feel like it's a "code smell". What I mean by that is that without the destructuring, I think the React code becomes much less readable; which makes me think that the React API for hooks wasn't designed well. I think they designed the API first and then worried about how people consumed it. If—and this is just a guess—they had worried about making it easy to consume first, then figured out the internals, React devs might not have to use destructuring nearly as much. Of course, they might not have ended up with Hooks that way.... which would be fine by me, I'm much more of a proponent of Class-based components... but now we're really off in the weeds 😆I like that destrucrturing exists; but, I've not yet fully embraced it in my workflows.
Since we're on the topic of "things you can do in CF that you can do in other languages", one thing I've been playing around with lately in my code is omitting the keys in a Struct where the key and value are the same. So instead of doing:
... I'll just do:
Which I think has been in JavaScript for quite a while. This may be an ACF-only feature, I'm not exactly sure.
@Ben Nadel,
Yes. Functional React seems like a reaction to me.
It's like trying to place a square peg into a round hole.
Class component based React made far more sense, to me, and this is borne out by the amount of hooks, the core team has had to build, to patch all the problems that functional React, has created.
And, I'm definitely with you on the conciseness of using matching Struct key/value names. I use this all time in JavaScript.
Saves a bit of space and improves readability. 🙂
Post A Comment — ❤️ I'd Love To Hear From You! ❤️