Skip to main content
Ben Nadel at Scotch On The Rocks (SOTR) 2011 (Edinburgh) with: Matthew Bourke
Ben Nadel at Scotch On The Rocks (SOTR) 2011 (Edinburgh) with: Matthew Bourke

Why CFML Can't "Just" Compile Variable Scoping Into The Byte Code

By
Published in — Comments (3)

In your ColdFusion code, if you reference a variable without an explicit scope, the ColdFusion runtime looks through a series of scopes in order to find the variable value. Over the years — for security reasons — the number of traversed scopes has been greatly limited (this is a "Good Thing™"). But, looking up a variable value is still work; and comes with a small performance penalty. In the vast, vast, vast majority of cases, this overhead is negligible. So much so that my default approach is to make my code maximally readable (ie, use unscoped variables); and then only worry about explicit scoping in hot paths within my ColdFusion application.

That said, given the fact that there is some overhead for unscoped variables, I briefly wondered why the CFML engines don't just "bake" scoping into the compiled byte code. Meaning, when the compiler creates the AST (Abstract Syntax Tree) and comes across an unscoped variable, why doesn't the compiler just look at the context in order to figure out what scope the developer is intending to consume; and then add the explicit scoping at compile time.

After thinking about this for a few minutes, it became obvious that the compiler simply doesn't have enough context given the current flexibility of the ColdFusion runtime. Part of what makes ColdFusion so magical is that it is so dynamic. And with that magic comes some compromises in performance.

Here's a quick demonstration of why compile-time scoping isn't possible. In the following CFML code, I have a single Function that's invoked four times. Each time the function dumps-out a value variable. And you'll see that in each invocation, the value is pulled from a different scope at runtime:

<cfscript>

	// ONE: The value is NOT defined yet anywhere.
	test();

	// TWO: The value is defined in the page scope.
	variables.value = "In the <mark> VARIABLES </mark> scope.";
	test();

	// THREE: The value is passed-in as an argument.
	test( value = "In the <mark> ARGUMENTS </mark> scope." );

	// FOUR: The value is appended to the local scope.
	test( copyToLocal = { value: "In the <mark> LOCAL </mark> scope." } );

	// ------------------------------------------------------------------------------- //
	// ------------------------------------------------------------------------------- //

	// Showcasing why "scoping" can't be compiled into the byte-code.
	private void function test() {

		local.append( copyToLocal ?? {} );

		writeOutput( "<p> VALUE: #( value ?? 'undefined' )# </p>" );

	}

</cfscript>

As you can see, the test() function is the same code for all four invocations. And yet, each invocation gives us a different outcome due to the dynamic scope chain traversal:

Four `test()` invocations showing the `value` pulled from different scopes (or undefined) in each invocation.

As you can see, each test() invocation pulls value out of a different scope at invocation time / runtime. There's really no way for the CFML compiler to know what this code will do when it's executed.

That said, this CFML code is purposefully dynamic for the demonstration. I'm sure there would be many places where the context is sufficient to automatically apply scoping. And who knows, maybe the CFML compiler is already doing that where is can. But it's definitely not a technique that can be applied writ large.

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

Reader Comments

319 Comments

Only took you a few minutes to realize why it couldn't?! Would have taken me much longer! 🤣🤣

I thought the premise was a great question.

I also appreciate your typos 'where is can'. I find them comforting because I know they were written by a falible human (see what I did there... using only one L?). Keep it human!

16,306 Comments

@Chris,

Ha ha, in honor of our shared humanity, I will leave in all typos 😜

Also, I probably should have deleted value after test TWO in order to demonstrate that it wasn't accidentally being picked up:

// TWO: The value is defined in the page scope.
variables.value = "In the <mark> VARIABLES </mark> scope.";
test();
// Delete the `value` variable / property so it doesn't pollute next tests.
variables.delete( "value" );

Running this gives the same outcome (just a more robust proof).

As a side note, I'm loving that I can finally use cfs ("CF Script") as a fenced code-block flag. Meaning, the CFML code samples can be color-coded / syntax highlighted even without a surrounding <cfscript> tag. Small ergonomic victory.

319 Comments

@Ben,

The cfs code blocks are a useful AI victory for sure! Congrats on that success 🙌

Deleting the var definitely tightens up the demo.

And I didn't realize you could reference named arguments, even though the function signature doesn't define them 🧐

Post A Comment — I'd Love To Hear From You!

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
Managed ColdFusion hosting services provided by:
xByte Cloud Logo