ColdFusion Muse

IsDefined() Vs. StructKeyExists() - The Nuances of CFMX Structures

Mark Kruger September 8, 2005 6:26 PM Coldfusion MX 7, Coldfusion Tips and Techniques Comments (7)

There's a very interesting post and discussion on Brian Kotek's blog today dealing with the use of the function "isDefined( )" vs "structKeyExists( )". If you've been programming since the days of CF 4.x and "parameterexists()" you'll know that using isDefined() can be a delicate experience at times. It becomes especially tricky in CFMX where the way variables are initiated has changed. In the old days you could have a variable in the variables scope that included a period in the name, and unless you specifically called "structNew()" it would stand as primitive variable in the variables scope. Take this Example:

CF 5 Example 1

<cfset foo = 1>
   <cfset foo.bar = 2>
This creates 2 variables in the variables scope - "foo" and "foo.bar". Don't let the period fool you! It's a regular primitive variable in the variables scope - no structure exists called "foo". To put it another way, you have created "variables['foo']" and "variables['foo.bar']" (even though we know the variables structure wasn't introduced till cfmx).

Try the same code on CFMX and what happens? You get an error of course. Why? Because you have taken a primitive variable "foo" that was already initiated and treated it as if it were a structure. That's how CFMX treats the period in the set statement - an implicit call to structNew().

To illustrate, try this code on CFMX:

CFMX Example

<cfset foo.var1.var2 = 'test'>

<Cfdump var="#foo#">
Notice that CMFX builds a structure "foo" with a key "var1" containing another structure with a key "var2" containing the string "test". On a CF 5 server the code would error out on the cfdump complaining that the variable "foo" doesn't exist.

Structure Fun

This behavior on CFMX can lead to some interesting consequences. I'll leave it to you to read Brian's post and the insightful comments, but let me summarize (or as my dad the Pentecostal Preacher used to say "as I continue to close"). Using structures in CFMX you can get around the limitations on variable naming. As you know, variable names in CFMX must start with a letter - for example, Code like:

<Cfset 33Foo = 'test'>
...would throw an error and complain that "33Foo" is not a valid identifier. However, CF is not really picky at all about the "key" identifiers in a structure. Pretty much anything you want to throw in there will work - numbers, spaces, special characters etc. are all fair game. Consider the following on CFMX:

This Code Errors Out

<Cfset 33Foo = 'test'>
<Cfset Foo* = 'test'>
<Cfset *Foo = 'test'>

This Code Works fine

<cfset variables['33Test'] = 'hello world'>
<cfset variables['Test*'] = 'hello world'>
<cfset variables['foo Test'] = 'hello world'>
So now you have a variable placed in the "variables" scope whose name doesn't conform to the stated standard for variable names. You can still use them as long as you scope them with bracket notation. Now using non-compliant names is a very bad idea, but because CF won't complain about it, you could introduce bugs accidentally. If you are like me, you have a great deal of code that builds structures out of "unknown" or "runtime" key names. This makes the rules for writing that kind of code easier - but harder to debug. Oh, and just in case you thought I was kidding about CF taking "everything" as a key name, check this out:

Bad Code that Works

<!--- special characters --->
<cfset variables['adflkjlekruoiauofiuoidufaelkruios sa()%$*@$*'] = 'hello world'>
<!--- how about line feeds?? --->
<Cfset lFeed = chr(10) & chr(13)>
<cfset variables['*test' & lFeed] = 'hello world'>
As you can see, pretty much everything is fair game.

Using isDefined()

I'm not complaining about how everything is a structure. I like the fact that virtually everything is accessible as an object with validation functions and the like. However, this "feature" of CFMX has an impact on the use of the "isDefined()" function. If you allow non standard variable names as keys in your structure, and then use "isDefined()" to try and test against 1 key or another you may get an unexpected result. This is the point made in Brian's blog so I won't belabor it here except to give 1 example.

<cfset foo = "blue">
<Cfset variables['33foo'] = "Red">

<cfoutput>
   
   #isDefined('variables.foo')#   - returns yes<br>
   #isDefined('variables.33foo')# - throws an error<br>
   #isDefined("variables['33foo']")# - Throws an error<br>
</cfoutput>

The point to emphasize here (and the point emphasized by the comments on Brian's blog) is that isDefined() checks not just if a variable exists, but if it is "syntactically correct". That's why, when dealing with structures (and almost everything is a structure) you should avoid isDefined() in favor of "structKeyExists()".

In the case of our test above:

<cfset foo = "blue">
<Cfset variables['33foo'] = "Red">

<cfoutput>
   
   #structKeyExists(variables,'foo')#   - returns yes<br>
   #structKeyExists(variables, '33foo')# - returns yes<br>
</cfoutput>

Now we are able to get the "proper" result from our bad code (ha).

To recap - isDefined() is an inferior choice to structKeyExists() when dealing with "runtime" variables (where the name is not pre-determined) because it will throw an error even when the code to set the variable into the structure does not. But the larger lesson is, strive to ensure that your variable names conform to the stated standard.

  • Share:

7 Comments

  • Brian Kotek's Gravatar
    Posted By
    Brian Kotek | 9/8/05 9:57 PM
    It is interesting. Also gets a bit confusing because I don't consider a structure key named something like '7' to be invalid. But it is invalid as a variable name. Basically, be careful with your variable names and don't use isDefined(). ;-)
  • Damien's Gravatar
    Posted By
    Damien | 9/9/05 10:10 AM
    How about comparing it against structKeyExists()?
  • Phillip Senn's Gravatar
    Posted By
    Phillip Senn | 1/25/06 12:57 PM
    I don't know.
    You're saying to use an obfuscation to cover yourself in case you use invalid variable names.
    Reminds me of putting in a global error trap while debugging - you'd rather have to bug show up instead of being caught by the global error trapper.
    I'd rather have the isDefined not work and find the invalid variable name causing the problem.
  • Mkruger's Gravatar
    Posted By
    Mkruger | 1/25/06 1:27 PM
    Phil,

    That's most definitely NOT what I'm trying to say. Note in the last paragraph:

    "...But the larger lesson is, strive to ensure that your variable names conform to the stated standard. "

    I'm not saying anything about obfuscation. Variable names (especially those from user input) should conform to the nameing convention - no doubt.
  • William Broadhead's Gravatar
    Posted By
    William Broadhead | 4/3/07 8:39 PM
    That is an interesting set of comments. Aside from the fact that I concur with using regularly named variables and keys, what is really FASTEST? That matters as much to me, as long as everyone is coding to standards otherwise. Is isDefined not as 'correct' to use as structKeyExists? I would say no. Not until Macrodobe says it is defunct or deprecated because structKeyExists is BETTER (and preferrably shows/explains why it is REALLY better performance wise). So I played around with that a bit more as our dev group is trying to decide what REALLY is best practise....

    Short answer: structKeyExists is SLOWER and has no advantage other than forcing coder to use scoping, which s/he should do anyway and assuming you don't use questionable key or variable names...

    Long Answer:

    whereas for the examples below url.pageid is a real url variable

    STRUCTKEYEXISTS:
    structKeyExists is pretty much the same speed no matter what you throw at it, keys can be real or not (url, 'pageid' and url, 'pageadfasdf' are about the same, no big diff one way or the other)
    and if you throw a scope at it that does not exist, it throws, so it forces you to be careful about scoping.

    ISDEFINED:
    isDefined is super fast when the scope exists and you pass in a scope (url.pageid),
    however, it is extremely extremely mega super slow when you pass in a non-existant scope (urlss.pageid).
    it is also super super mega slow if you pass in a non-scoped non-existant variable (pagelksjdljsdf)
    it is quite fast, on average 10 to 20% faster than structKeyExists, when you pass in a SCOPED variable, existant or not (url.pageid or url.aoiausldkjf)

    So what does that mean in real life?
    Well, as long as you are asking for a SCOPED variable that is extant, and you don't mispell the scope (url for instance), isDefined will be faster on average....
    If you are looking for a NON-SCOPED variable, you have no choice but to live with isDefined.... hum....which can be very very slow when the variable does not exist.
    If you want to make sure you scope all your vars, use structKeyExists and live with a small speed hit, but be assured it is scoped to a real scope (or throw an error).
    So for me, in real life use, that means isDefined is probably the better choice as it is overall going to be faster as long as i can spell my scope correctly.
    It also is the only choice to use when looking for multiple scope variables such as FORM.ID vs URL.ID and your page/function can take either and want to simply ask isDefined('ID')
    but if you want to prioritize one over the other (which you probably should) then you can again use either define/struct
    where you have a little bit of script that checks each (form.id and then url.id) and assigns a variables.myID which is then passed in to page/function to use....
    again, this would indicate isDefined would be preferred as it would be faster for that call too, as long as it is scoped and the scope exists.
    So I can't find any reason to use structKeyExists other than it might seem like better code because it forces scoping....
    How sad... it seemed like it should be faster as it specifies the scope....

    here is sample code for you to run and see for yourself.... i'd love to know if you find anything different than what I did by platform or other (I am using CFMX702 on a WINDOWS platform). Is linux version any faster? Anyone know what actually happens at the compiled java level?)

    <CFSCRIPT>
    ITERATIONS = 100000;
    writeoutput('ITERATIONS : ' & ITERATIONS & '<BR>' );
    i=0;
    j=0;
    k=0;
    ttt1 =0;
    ttt2 =0;
    t1c =0;
    t2c=0;

    for(k; k lte ITERATIONS; k=k+1){

    T1 = getTickCount();
    for(i; i lte ITERATIONS; i=i+1){
    if(isDefined('url.asdfpageid')){
    //do nothing
    }
    }
    T2 = getTickCount();
    TT1 = T2-T1;
    ttt1=ttt1+TT1;


    T3 = getTickCount();
    for(j; j lte ITERATIONS;j=j+1){
    if(structKeyExists(url, 'pageeid')){
    //do nothing
    }
    }
    T4 = getTickCount();
    TT2 = T4-T3;
    ttt2=ttt2+TT2;
    if(TT1 gt 0){t1c = t1c+1;}
    if(TT1 gt 0){t2c = t2c+1;}

    if(TT1 gt 0 or TT2 gt 0){writeoutput('Iteration K: ' & k & ' isDefined: ' & TT1 & 'ms'); writeoutput(' structKeyExists: ' & TT2 & 'ms<br>');}


    }

    writeoutput('<BR>Times isDefined used time: ' & t1c);
    writeoutput('<BR>Times structKeyExists used time: ' & t2c);

    writeoutput('<BR>FINAL isDefined: ' & TTT1 & 'ms');
    writeoutput('<BR>FINAL structKeyExists: ' & TTT2 & 'ms');

    </CFSCRIPT>
    <br>



    <!--- The cfchart --->
    <cfchart format="flash" xaxistitle="function" yaxistitle="Loading Time">
    <cfchartseries type="bar" serieslabel="isDefined">
    <cfchartdata item="isDefined" value="#variables.TTT1#">
    </cfchartseries>
    <cfchartseries type="bar" serieslabel="structKeyExists">
    <cfchartdata item="structKeyExists" value="#variables.TTT2#">
    </cfchartseries>
    </cfchart>
  • William Broadhead's Gravatar
    Posted By
    William Broadhead | 4/3/07 8:39 PM
    That is an interesting set of comments. Aside from the fact that I concur with using regularly named variables and keys, what is really FASTEST? That matters as much to me, as long as everyone is coding to standards otherwise. Is isDefined not as 'correct' to use as structKeyExists? I would say no. Not until Macrodobe says it is defunct or deprecated because structKeyExists is BETTER (and preferrably shows/explains why it is REALLY better performance wise). So I played around with that a bit more as our dev group is trying to decide what REALLY is best practise....

    Short answer: structKeyExists is SLOWER and has no advantage other than forcing coder to use scoping, which s/he should do anyway and assuming you don't use questionable key or variable names...

    Long Answer:

    whereas for the examples below url.pageid is a real url variable

    STRUCTKEYEXISTS:
    structKeyExists is pretty much the same speed no matter what you throw at it, keys can be real or not (url, 'pageid' and url, 'pageadfasdf' are about the same, no big diff one way or the other)
    and if you throw a scope at it that does not exist, it throws, so it forces you to be careful about scoping.

    ISDEFINED:
    isDefined is super fast when the scope exists and you pass in a scope (url.pageid),
    however, it is extremely extremely mega super slow when you pass in a non-existant scope (urlss.pageid).
    it is also super super mega slow if you pass in a non-scoped non-existant variable (pagelksjdljsdf)
    it is quite fast, on average 10 to 20% faster than structKeyExists, when you pass in a SCOPED variable, existant or not (url.pageid or url.aoiausldkjf)

    So what does that mean in real life?
    Well, as long as you are asking for a SCOPED variable that is extant, and you don't mispell the scope (url for instance), isDefined will be faster on average....
    If you are looking for a NON-SCOPED variable, you have no choice but to live with isDefined.... hum....which can be very very slow when the variable does not exist.
    If you want to make sure you scope all your vars, use structKeyExists and live with a small speed hit, but be assured it is scoped to a real scope (or throw an error).
    So for me, in real life use, that means isDefined is probably the better choice as it is overall going to be faster as long as i can spell my scope correctly.
    It also is the only choice to use when looking for multiple scope variables such as FORM.ID vs URL.ID and your page/function can take either and want to simply ask isDefined('ID')
    but if you want to prioritize one over the other (which you probably should) then you can again use either define/struct
    where you have a little bit of script that checks each (form.id and then url.id) and assigns a variables.myID which is then passed in to page/function to use....
    again, this would indicate isDefined would be preferred as it would be faster for that call too, as long as it is scoped and the scope exists.
    So I can't find any reason to use structKeyExists other than it might seem like better code because it forces scoping....
    How sad... it seemed like it should be faster as it specifies the scope....

    here is sample code for you to run and see for yourself.... i'd love to know if you find anything different than what I did by platform or other (I am using CFMX702 on a WINDOWS platform). Is linux version any faster? Anyone know what actually happens at the compiled java level?)

    <CFSCRIPT>
    ITERATIONS = 100000;
    writeoutput('ITERATIONS : ' & ITERATIONS & '<BR>' );
    i=0;
    j=0;
    k=0;
    ttt1 =0;
    ttt2 =0;
    t1c =0;
    t2c=0;

    for(k; k lte ITERATIONS; k=k+1){

    T1 = getTickCount();
    for(i; i lte ITERATIONS; i=i+1){
    if(isDefined('url.asdfpageid')){
    //do nothing
    }
    }
    T2 = getTickCount();
    TT1 = T2-T1;
    ttt1=ttt1+TT1;


    T3 = getTickCount();
    for(j; j lte ITERATIONS;j=j+1){
    if(structKeyExists(url, 'pageeid')){
    //do nothing
    }
    }
    T4 = getTickCount();
    TT2 = T4-T3;
    ttt2=ttt2+TT2;
    if(TT1 gt 0){t1c = t1c+1;}
    if(TT1 gt 0){t2c = t2c+1;}

    if(TT1 gt 0 or TT2 gt 0){writeoutput('Iteration K: ' & k & ' isDefined: ' & TT1 & 'ms'); writeoutput(' structKeyExists: ' & TT2 & 'ms<br>');}


    }

    writeoutput('<BR>Times isDefined used time: ' & t1c);
    writeoutput('<BR>Times structKeyExists used time: ' & t2c);

    writeoutput('<BR>FINAL isDefined: ' & TTT1 & 'ms');
    writeoutput('<BR>FINAL structKeyExists: ' & TTT2 & 'ms');

    </CFSCRIPT>
    <br>



    <!--- The cfchart --->
    <cfchart format="flash" xaxistitle="function" yaxistitle="Loading Time">
    <cfchartseries type="bar" serieslabel="isDefined">
    <cfchartdata item="isDefined" value="#variables.TTT1#">
    </cfchartseries>
    <cfchartseries type="bar" serieslabel="structKeyExists">
    <cfchartdata item="structKeyExists" value="#variables.TTT2#">
    </cfchartseries>
    </cfchart>
  • William Broadhead's Gravatar
    Posted By
    William Broadhead | 4/3/07 8:40 PM
    That is an interesting set of comments. Aside from the fact that I concur with using regularly named variables and keys, what is really FASTEST? That matters as much to me, as long as everyone is coding to standards otherwise. Is isDefined not as 'correct' to use as structKeyExists? I would say no. Not until Macrodobe says it is defunct or deprecated because structKeyExists is BETTER (and preferrably shows/explains why it is REALLY better performance wise). So I played around with that a bit more as our dev group is trying to decide what REALLY is best practise....

    Short answer: structKeyExists is SLOWER and has no advantage other than forcing coder to use scoping, which s/he should do anyway and assuming you don't use questionable key or variable names...

    Long Answer:

    whereas for the examples below url.pageid is a real url variable

    STRUCTKEYEXISTS:
    structKeyExists is pretty much the same speed no matter what you throw at it, keys can be real or not (url, 'pageid' and url, 'pageadfasdf' are about the same, no big diff one way or the other)
    and if you throw a scope at it that does not exist, it throws, so it forces you to be careful about scoping.

    ISDEFINED:
    isDefined is super fast when the scope exists and you pass in a scope (url.pageid),
    however, it is extremely extremely mega super slow when you pass in a non-existant scope (urlss.pageid).
    it is also super super mega slow if you pass in a non-scoped non-existant variable (pagelksjdljsdf)
    it is quite fast, on average 10 to 20% faster than structKeyExists, when you pass in a SCOPED variable, existant or not (url.pageid or url.aoiausldkjf)

    So what does that mean in real life?
    Well, as long as you are asking for a SCOPED variable that is extant, and you don't mispell the scope (url for instance), isDefined will be faster on average....
    If you are looking for a NON-SCOPED variable, you have no choice but to live with isDefined.... hum....which can be very very slow when the variable does not exist.
    If you want to make sure you scope all your vars, use structKeyExists and live with a small speed hit, but be assured it is scoped to a real scope (or throw an error).
    So for me, in real life use, that means isDefined is probably the better choice as it is overall going to be faster as long as i can spell my scope correctly.
    It also is the only choice to use when looking for multiple scope variables such as FORM.ID vs URL.ID and your page/function can take either and want to simply ask isDefined('ID')
    but if you want to prioritize one over the other (which you probably should) then you can again use either define/struct
    where you have a little bit of script that checks each (form.id and then url.id) and assigns a variables.myID which is then passed in to page/function to use....
    again, this would indicate isDefined would be preferred as it would be faster for that call too, as long as it is scoped and the scope exists.
    So I can't find any reason to use structKeyExists other than it might seem like better code because it forces scoping....
    How sad... it seemed like it should be faster as it specifies the scope....

    here is sample code for you to run and see for yourself.... i'd love to know if you find anything different than what I did by platform or other (I am using CFMX702 on a WINDOWS platform). Is linux version any faster? Anyone know what actually happens at the compiled java level?)
    <code>
    <CFSCRIPT>
    ITERATIONS = 100000;
    writeoutput('ITERATIONS : ' & ITERATIONS & '<BR>' );
    i=0;
    j=0;
    k=0;
    ttt1 =0;
    ttt2 =0;
    t1c =0;
    t2c=0;

    for(k; k lte ITERATIONS; k=k+1){

    T1 = getTickCount();
    for(i; i lte ITERATIONS; i=i+1){
    if(isDefined('url.asdfpageid')){
    //do nothing
    }
    }
    T2 = getTickCount();
    TT1 = T2-T1;
    ttt1=ttt1+TT1;


    T3 = getTickCount();
    for(j; j lte ITERATIONS;j=j+1){
    if(structKeyExists(url, 'pageeid')){
    //do nothing
    }
    }
    T4 = getTickCount();
    TT2 = T4-T3;
    ttt2=ttt2+TT2;
    if(TT1 gt 0){t1c = t1c+1;}
    if(TT1 gt 0){t2c = t2c+1;}

    if(TT1 gt 0 or TT2 gt 0){writeoutput('Iteration K: ' & k & ' isDefined: ' & TT1 & 'ms'); writeoutput(' structKeyExists: ' & TT2 & 'ms<br>');}


    }

    writeoutput('<BR>Times isDefined used time: ' & t1c);
    writeoutput('<BR>Times structKeyExists used time: ' & t2c);

    writeoutput('<BR>FINAL isDefined: ' & TTT1 & 'ms');
    writeoutput('<BR>FINAL structKeyExists: ' & TTT2 & 'ms');

    </CFSCRIPT>
    <br>



    <!--- The cfchart --->
    <cfchart format="flash" xaxistitle="function" yaxistitle="Loading Time">
    <cfchartseries type="bar" serieslabel="isDefined">
    <cfchartdata item="isDefined" value="#variables.TTT1#">
    </cfchartseries>
    <cfchartseries type="bar" serieslabel="structKeyExists">
    <cfchartdata item="structKeyExists" value="#variables.TTT2#">
    </cfchartseries>
    </cfchart>
    </code>