Blog

21Jun
2009
The New ColdFusion LOCAL Scope

ColdFusion developers have long known to ensure that local variables remain local by using the "var" keyword to define them. Here's an example:

view plain print about
1<cffunction name="myFunction">
2    <cfset var mySafeVariable = 1>
3    <cfset myUnsafeVariable = 1>
4</cffunction>

In the above snippet, two variables are created. The first variable uses the var keyword to ensure that the variable is local to the function, and if the same variable name existed elsewhere it won't be overwritten. The second variable does not use var, and as such is not local, and variable conflicts can indeed occur. And so, when creating user defined functions or ColdFusion Component methods, the rule has always been to always prefix local variables with "var".

But what exactly is a var variable? What scope is it in? And how is it accessed using explicit scope notation? The answers to these questions are somewhat unclear, partially because the local var scope is not used like any other ColdFusion scope which is always designated by using a scope prefix (VARIABLES.myVariable, FORM.myVariable, SESSION.mayVariable, etc.).

ColdFusion Centaur simplifies the use of local scopes (without breaking existing code) by explicitly defining a local scope that it intuitively named LOCAL. Here is the same local variable set above, but using the LOCAL scope instead of var:

view plain print about
1<cfset LOCAL.mySafeVariable = 1>

But what if you do indeed use var? Well, it'll just work. Look at the following two <CFSET> statements:

view plain print about
1<cfset local.myVar1=1>
2<cfset var myVar1=1>

Both of these code snippets do the exact same thing. Variables with the var prefix are now automatically defined in the LOCAL scope. So a variable defined as:

view plain print about
1<cfset var mySafeVariable = 1>

can be safely accessed as:

view plain print about
1<cfoutput>#LOCAL.mySafeVariable#</cfoutput>

It's clean, it's simple, it's intuitive, and it's fully backwards compatible. And yes, if you have a variable named "local" it'll still work, the variable will just become LOCAL.local, and as the LOCAL scope is in the default evaluation chain it'll just work.

Oh, one other change. The "var" keyword used to only be supported at the top of functions and methods, so all local variables had to be defined upfront. This limitation has been removed in ColdFusion Centaur (and I actually have mixed feelings about this, but so be it).

Related Blog Entries

Comments (33)



  • Tony

    What if you have an argument called "local"? How will that evaluate? Also, are arguments considered local as well or is the arguments scope kept completely separate from the local scope?

    #1Posted by Tony | Jun 21, 2009, 09:42 PM
  • Ben Forta

    ARGUMENTS is separate, and functions as it did before. So an argument named local would be ARGUMENTS.local, as before.

    --- Ben

    #2Posted by Ben Forta | Jun 21, 2009, 09:50 PM
  • Raymond Camden

    And one should note - you still need to worry about var scoping - it's just that now you have another way to do it - the local scope. If you forget to use the local scope, and var scope, then you will still run into trouble. I'd love to see CF simply treat an un-local scoped, un-var'ed, variable as a local variable anyway. If I really want a variable to leak, make me do variables.x=something.

  • Terrence Ryan

    Ray, as much as it would be great if we could do that, it would majorly break backwards compatibility.

    Better to wish for time travel and have someone go back and change the original behavior of var scope.

  • Ken Gladden

    I would have liked to have kept the requirement to have the variable declaration kept up front. I would like it if Eclipse/bolt gave you an error if you used a <cfset > on an undeclared variable.

  • Seth

    I am confused on the backwards compatibility. Wasn't the default scope "variables" before? if this has changed won't there be some compatibility issues (with poorly formatted code)?

    Will <cfset myCFCVar = 1 /> still be the same as <cfset variables.myCFCVar = 1 /> ?

    #6Posted by Seth | Jun 22, 2009, 12:08 AM
  • Russ S.

    Here's what Adobe should do:
    Take all the great ideas that can't be used due to backward compatibility and put them into an brand new engine. It seems like if they don't do it at some point, then eventually they'll end up with a slow, out-dated engine and someone else will be happy to take all those great ideas and put them to work.

    And really, no matter how backward compatible the newest version of Coldfusion is, everyone expects that some of their code will break when they upgrade. Seems like Adobe knows it too, seeing as how they provide the version migration tool in CF Administrator.

    So if they are breaking backward compatibility anyways, I say just go for it. Give us the cool new features and let US worry about fixing broken code.

    (Honestly, one of the things I like about upgrading Coldfusion is that it gives me an excuse to rewrite nasty old code.)

    #7Posted by Russ S. | Jun 22, 2009, 02:53 AM
  • Michiel Bakker

    "And yes, if you have a variable named "local" it'll still work, the variable will just become LOCAL.local, and as the LOCAL scope is in the default evaluation chain it'll just work."

    How does the following evaluate then?
    <cfset var local = structnew()>
    <cfset local.foo = "bar">

    Do we have local.foo or local.local.foo?

  • George Bridgeman

    How is it backward compatible?

    Michiel gave a good example. I think that code won't work on CF9 as it'll look in the local scope (not the var scoped local struct), not find a variable called foo in the scope, and throw an exception. We have swaths of code written in the same way and I'm keen to see how CF9 will/won't cope with this.

    Also, even if we have a var scoped variable called foo, CF9 will put this in the local scope, but how will our old code which uses that variable work, as it won't have the local scope prefix? For example,
    <cfset var foo = "bar" />
    <cfset var x = "" />
    ... some code ...
    <cfset x = "foo now " & foo />
    There's no local prefix on the last cfset so where will CF9 look first to resolve the variables? In which order will it check each scope? Will it check the variables scope first? Will we have to go over tens of thousands of lines of code and put local. in front of every var scoped variable?

    George.

    #9Posted by George Bridgeman | Jun 22, 2009, 05:36 AM
  • Ben Forta

    Seth, that is correct.

    Michael, it'll be local.local.foo.

    George, the reference to x will automatically local.x, so in your example x will be "foo now bar", which should be what you want.

    --- Ben

    #10Posted by Ben Forta | Jun 22, 2009, 06:23 AM
  • Dan Vega

    Ben,
    I am curious why you have mixed feelings about the ability to use the var keyword anywhere? I think this is very important when you need to work with loops in a function and you need to create variables in a loop that need to stay local to it. I for 1 am pretty happy about this & the local scope (though all Ill still probably always use var).

    #11Posted by Dan Vega | Jun 22, 2009, 09:12 AM
  • Steven Esser

    Hmm this is going to be very confusing with multiple LOCAL.local.local :-)
    But yes I think I have plenty of old code in normal: <cfset varDate=NOW()>
    and also a lot of <cfset variables.varDate=Now()>

    and I saw somewhere also at railo that you need to be explicit on variables, but I prefer to sometimes make use of the checking of <cfif isdefined("searchterm")> instead of <cfif isdefined("form.searchterm")> simply because certain pages have both form and url variables coming in for the same content.
    Yeah or you have to put these again into local. variables before processing...
    I guess it would be smarter to have scopes on different things.. so a scope in a function that only keeps track of provided arguments + local variables.. same with components, custom tags etc.
    Seperate the stuff, much easier.

  • Adam Tuttle

    What happens in this case?

    <cfset var local = structNew() />
    <cfset var foo = "bar" />
    <cfset local.foo = "foobar" />
    <cfreturn local.foo />

    Which value will be returned? I assume it will be "foobar" (because perhaps you're checking for a local.local to help preserve backward compatibility), but what if I later want to access var foo? Can that be done *without* dropping the explicit scope notation?

    That would certainly look more like CF8, but I for one am pretty anal about using explicit scope notation wherever possible, so being forced to leave it off (if that's the case) doesn't sound like a great idea to me.

  • Jalpino

    I'm pretty bummed to see that 90%+ of my CFCs will have to be refactored to work in the new release. Like most others, I started using the

    <cfset var local = structNew()>

    definition years ago to get around having to explicitly declare each and every local variable. Like Ray I feel that un-scoped variables created within the context of a function should automatically be locally scoped.

    Its my opinion, but I would bet that the majority of developers who have statements like in their methods

    <cfset foo = "bar">

    did not intend for them to be set into the shared private (variables) scope. I think if anything, by auto-locallizing those un-scoped variable declarations that Adobe might implicitly be helping the developer by making those unintentional variables declarations thread safe.

    #14Posted by Jalpino | Jun 22, 2009, 04:37 PM
  • Steve Nelson

    What about the local scope with a cfquery then referencing the query with a QoQ? i.e.

    <cfquery name="local.whatever"....>
    select * from table
    </cfquery>
    <cfquery .... dbtype="query">
    select * from local.whatever
    </cfquery>

    I haven't tried this in a while, but it usually yells at me when i've done that in the past.

  • jalpino

    I totally didn't read the entire post close enough, glad to know I won't have to refactor.

    "the variable will just become LOCAL.local, and as the LOCAL scope is in the default evaluation chain it'll just work"

    #16Posted by jalpino | Jun 22, 2009, 06:25 PM
  • Dale Fraser

    @Ben

    "And yes, if you have a variable named "local" it'll still work, the variable will just become LOCAL.local"

    This is not correct, our app uses

    <cfset var local = {} />

    Then we use local everywhere, this still works, but that created local scope goes into variables and not local.local and there is no way of accessing it other than perhaps going variables.local.

    This could break something if elsewhere in your app you use the local variable, it will be overwritten

  • Dale Fraser

    One thing that I like about this new scope is that it also contains the arguments collection.

    Thus arguments are contained within local.

    Thus dumping local, dumps all local variables and arguments. scope

  • asha

    We actually ignore the statement <cfset var local = structnew()>.So local.foo is set to Local scope and it will not be Local.local.Inorder to access foo use Local.foo or just foo.This we do because initially var could be declared only at the start of the function declaration so many people used <cfset var local = structnew()> to maintain their local variables . Hence now their code will not break(which is most of the cases) but on the flip side if any one has <cfset var local = "hello"> this will also get ignored and might break their code .We have also documented this behaviour.

    #19Posted by asha | Jun 23, 2009, 10:40 AM
  • Jim Pickering

    I too have mixed feelings about using var-scoped variables anywhere inside of a cffunction. I enjoyed the forced discipline of defining them all at the top of a function. It kept functions clean and organized - as if listing an index of variables used in the method. Those of us working on teams will now have to sift through methods, searching for var-scoped variables throughout; unless, of course, some coding standards are put in place.

  • Julian Halliwell

    @Steve Nelson

    "What about the local scope with a cfquery then referencing the query with a QoQ?"

    Steve, the reason it yells is that "local" is a reserved word in QoQ. You just need to escape it using square brackets:

    select * from [local].whatever

    I wonder if this issue will go away with CF9 (never understood exactly what "local" is reserved *for* in QoQ).

  • Phillip Senn

    It's a nit pick, but maybe something that someone writing a code generator will want to follow:
    As long as we're talking about local scope vs. variables scope, we should probably put result="local.result" in the cfquery command.
    Otherwise, a variable called cfquery.executiontime gets created in the variables scope.

  • William

    So, I am having an issue understanding the reasoning behind this change. I understand 'how' this works, but why was this change done? What benefit does this change give? The novice will 'still' need to follow the same scoping rules and the same 'var' rules, so it doesn't solve any common novice rules.

    #23Posted by William | Jul 8, 2009, 02:21 PM
  • Raymond Camden

    Yes, you still have to var scope your variables. This is still an improvement though as previously, the values in this scope were not accessible like every single other scope in CF.

  • Phillip Senn

    > you still have to var scope your variables
    That's not how I read this blog post.

    I read it that cf will explicitly define a local scope.

    This means that every variable should now be scoped with one of the named scopes like local, variables, cgi, file, url, form, cookie, client. If you ever see a variable that isn't explicitly scoped, it should be a red flag.

  • Adam Tuttle

    Phillip, I think you're taking the words a little *too* literally. When they say they're explicitly defining a local scope, they just mean they are adding a new special scope/keyword to *explicitly* access things in the function-local scope (instead of implying by not including a scope at all)...

    My preference is that everything should be scoped anyway, all of the time, but that's a stylistic choice.

    Maybe this can help some?

    http://fusiongrokker.com/post/deciphering-the-new-...

  • Esteban

    This example fails on CF 9...
    <cfset var local = StructNew()>
    <cfset local.availableProdItems = "">

    <cfquery name="local.getRights" datasource="#application.dsn#">
       SELECT P.productId
        FROM products P
    </cfquery>

    <cfset local.availableProdItems = ListAppend(local.availableProdItems, ValueList(local.getRights.productId))>

    Error Diagnostics: Complex object types cannot be converted to simple values. The expression has requested a variable or an intermediate expression result as a simple value. However, the result cannot be converted to a simple value. Simple values are strings, numbers, boolean values, and date/time values. Queries, arrays, and COM objects are examples of complex values.

    The most likely cause of the error is that you tried to use a complex value as a simple one. For example, you tried to use a query variable in a cfif tag.

    #27Posted by Esteban | Feb 18, 2010, 10:13 AM
  • Tristan Lee

    Wow, I just got to be so lucky to experience this non-sense today. As Dale mentioned, all of my CFCs use <cfset var local = {}> or sometimes
    <cfset var local = {foo = "bar"}> for default values. Now the scoping is incorrect as when I dump the variables scope, what was last set it local is now
    in my variables.local scope. So much for backward compatibility.

  • Walt

    We have an issue with just trying to access values we know exist in the local scope (in a cfm template, not a cfc).
    When we use isDefined("local.filter_#loopCount#"), it returns false.
    When using structkeyexists, it works fine.
    when putting local into request.newlocal, we can find the data using isDefined().

    What gives?

    #29Posted by Walt | Apr 29, 2010, 04:09 PM
  • Rob Morris

    Luckily for us, we used the convention

    <cfset var loc = {}>

    At the top of our functions. This was because it's shorter, still obvious, and works in query of queries normally without needing to be escaped. It also means that with CF9 we haven't had to change anything.

    We continue to use this convention because the LOCAL scope also includes "arguments"... so using our own scope is helpful as it allows us to cleanly isolate the vars.

    This is also the same reason we use an "instance" scope in cfcs, instead of storing directly in variables. This is one beef I have with the new ORM in CF 9; it's use of the root variables scope; workable but very annoying!

    #30Posted by Rob Morris | May 12, 2010, 01:20 PM
  • Vikas Patel

    Hi Ben,

    These days, I am working on ColdBox and I am doing testing with MockBox and MXUnit.

    During testing I found leakage of local variables via interesting case.

    I use this code in model's cfc

    <cfset var arr = ArrayNew(1)>
    <cfset var LOCAL = structNew()>

    <cfscript>
       LOCAL.myname = "abc";
       LOCAL.mycode = 511;
       LOCAL.priority = 1;
       
       arrayAppend(arr,duplicate(LOCAL));
       
       LOCAL.myname = "xyx";
       LOCAL.mycode = 521;         
       LOCAL.priority = 3;
       arrayAppend(arr,duplicate(LOCAL));
       
       return arr;
    </cfscript>

    When I dump the function result on eventHandler, I get the function arguments and model cfc as "this" structure.

    So what do you say? Should we really use this scope?
    Also I saw ben nadel's post regarding conflicting the local scope with query of query.

    Thanks.

  • Vikas Patel

    After spending half of the day, I posted my experience in our blog <a href="http://www.cfminds.com/post.cfm/work-around-coldfu...;. Please someone check, if I am doing something wrong.

  • Anastassios N. Hadjicrystallis

    Ben,
    today I faced a problem (bug?) on the use of var, which is exactly on the subject of the discussion here, and which shows that var is not practically indentical to local scope.
    Please see the code below:
    <!------>
    <cffunction name="Func1" returnType="string" output="no">

    <cfargument name="Argu1" type="numeric"   required="Yes">
    <cfargument name="Argu2" type="numeric"   required="Yes">

    <cfset local.Argu1 = 0>
    <cfset var Argu2 = 0>

    <cfreturn "">
    </cffunction>
    <!------>

    <cfset Ret = Func1( 1, 2 )>

    As you see I just declare two variables, and I do it in the first case using the local scope and in second case using the var. Running the code I get an error message !!! The error is:
    ---
    ARGU2 is already defined in argument scope.
    Use local to define a local variable with same name.
    ---

    The error happens in line
    <cfset var Argu2 = 0>

    which means the previous line
    <cfset local.Argu1 = 0>
    is OK.

    The error happens only in case the var variable has the same name (Argu2) with the argument variable Argu2, and although when doing exactly the same, using local, there is no problem.

    What I understand of this error is that var is not really a scope, and it's not indentical to local. Also the error means that "var Argu2" does not isolates Argu2 from the other scopes (eg Arguments) that's why it conflicts with Arguments.Argu2 and gives the error.
    I didn't find any documentation about it somewhere.
    I checked the code above in CF 9 and 10 and both give the same error.

    Is it a bug, is it an issue out of docs, or I miss something?

    Anastassios