Skip to content

Strict Vars

Jonathan Revusky edited this page Nov 21, 2023 · 6 revisions

Strict Variable Definition in FreeMarker 3

In older versions of FreeMarker, the way you set a variable in the template was with the #assign directive. You could also set a local variable in a macro using #local. Those directives still work if you have explicitly turned off strict variable definition. Specifically, you do that for a single template by putting:

 #ftl legacy_syntax

at the top. This can also be done globally from Java code via:

  Configuration.getDefaultConfiguration().setLegacySyntax(true);

Without that, you need to use the newer #var and #set directives, which I shall describe now.

Motivation

Has it ever occurred to you how fragile shell (or .bat) scripts tend to be? For example, I suppose most people reading this are Java hackers. Suppose you write some script to launch a Java app, with something like:

CLASPATH=~/myjavalibs:$CLASSPATH

What happens in the above is that CLASPATH is misspelled, so instead of redefining the existing CLASSPATH variable, prepending ~/myjavalibs to it, this line in effect declares a new variable called CLASPATH and the existing CLASSPATH variable remains unchanged. Then, of course, the script doesn't work as intended, but it can be not terribly obvious why.

How it works

What the the #set and #var directives do is they create a clear conceptual distinction between declaring a variable and setting its value. Admittedly, this is more typical of real programming languages, but I think it has value in this kind of templating language.

After all, the same problem exists in FreeMarker templates. Suppose you have an existing variable called name but you write:

[#assign Name = client.name]

and, rather than redefining the existing name variable, it creates a new variable called Name and sets that. Using #var/#set you need to declare the variable with the #var directive before using it. So, suppose you define a macro like so:

#macro myMacro
   #local name = client.name
...
/#macro

In the above, there is effectively a local variable called name declared and set. The preferred way to express this now is:

   #var name
   #set name = client.name

So if you misspell the variable, you will get an error. So, if you later write:

   #set Name = "Mr. " + client.name

you will hit a clear error since the variable name that was declared was name, not Name. With the older disposition, the above error just passes through. But now it gets caught.

Note also that the lines:

  #var name
  #set name = client.name

can be written more tersely (and this is surely how most people would write it!) as:

 #var name = client.name

So, in fact, using the newer #var/#set is not more verbose than the older #global/#local. (Quite the contrary, since #var and #set are shorter than #assign or #local! And, of course, in conjunction with the newer bracket-free syntax this tends to make templates in the newer style significantly shorter.)

When you declare a variable, it is taken to exist in the block where it is defined. So, if you have:

#if someCondition
  #var something = foo.bar
  ...
  ${something}
/#if

${something}

In the above, the final ${something} will be an error since the something variable only exists in the block in which it is defined. So the above will blow up except on the off chance that this scope (the one that contains the if block) contains a something variable. Another way of expressing the concept is that if you want the something variable to be visible, it needs to be defined outside the if block, as in:

#var something
...
#if someCondition
  #set something = foo.bar
  ...
  ${something}
/#if

${something}

Regardless, the bottom line is that you typically define a variable as locally as possible, so there is much less unintentional clobbering of existing variables via name clashes. Well... more like in a real programming language!

I initially considered making legacy_syntax true by default, to ease people's transition, but I decided finally, that I prefer to maximally encourage people to use the newer syntax. Not only is it less typing, but it should lead to more robust results!

Clone this wiki locally