Tcl Part I · Module 3 · Values, Variables, and Expressions
Why expr Arguments Belong in Braces
The previous chapter told you to brace every expression without saying why. The reason is worth a chapter because it is three reasons, and the first of them is not about style or speed — it is about getting the wrong answer. An unbraced expression is substituted by Tcl and then parsed again by expr, so your data gets evaluated twice. That produces arithmetic that is quietly incorrect, it will run commands that arrived inside a value, and it costs about ten times as much. One pair of braces removes all three.
Foundation14 min readTclPart IValuesexprSafety
Part I · Module 3 · Chapter 3.8 · Why expr Arguments Belong in Braces
1. The Rule
expr {$a * 2} ;# always
expr $a * 2 ;# neverIf you remember only the rule, you will write correct Tcl. The rest of this chapter is why, because the reasons change how you think about expr and about data in general.
2. What Each Form Does
Both forms reach expr, but by different routes.
Braced. Chapter 2.8: braces pass text through untouched. expr receives the characters you wrote and parses them with its own rules, substituting variables itself as it goes.
Unbraced. Tcl substitutes first — variables and commands — and then hands the resulting text to expr, which parses that.
So the unbraced form evaluates your data twice: once by Tcl, once by expr. Everything below follows from that.
3. Reason One: Wrong Answers
This is the reason to lead with, because it is not a style matter.
set a "2 + 3"
puts "unbraced: [expr $a * 2]"unbraced: 8You might have expected 16 — five times two. You got 8.
Here is why. Tcl substituted $a, producing the text 2 + 3 * 2. Then expr parsed that, and in expr's rules multiplication binds tighter than addition. So it computed 2 + (3 * 2), which is 8.
The parentheses you were relying on never existed. The value had them conceptually; the text did not.
Now the braced form:
set a "2 + 3"
puts [catch {expr {$a * 2}} m]
puts $m1
can't use non-numeric string as operand of "*"An error — and the right outcome. expr substituted $a itself, found the text 2 + 3 where it needed a number, and said so. You wanted to multiply something by two and the something was not a number. Being told that is far better than being handed 8.
4. This Is Chapter 2.11 Again
Chapter 2.11 established that Tcl substitutes once and never re-reads the result — unless you name a command that evaluates text.
expr is such a command. It parses text as an expression. So an unbraced expr is precisely the shape that chapter warned about: substitute a value into some text, then hand that text to something that will parse it again.
Braces close that door by making expr receive the expression you wrote rather than one Tcl assembled from your data.
5. Reason Two: It Will Run Commands in Your Data
If expr parses the text a second time, then command substitution in that text runs. This is not theoretical:
set userinput {1 + [puts "INJECTED"; set _ 0]}
puts "unbraced result: [expr $userinput]"INJECTED
unbraced result: 1The value contained a bracketed command, and it ran. The word INJECTED was printed by data, not by your program.
Braced, nothing runs:
set userinput {1 + [puts "INJECTED"; set _ 0]}
puts "about to evaluate"
puts [expr {$userinput * 2}]about to evaluate
can't use non-numeric string as operand of "*"No INJECTED. expr substituted the variable itself and found text that was not a number.
6. Reason Three: It Is About Ten Times Slower
The weakest of the three arguments, included because it is often the only one people have heard, and because it can be measured rather than asserted.
Tcl can compile a braced expression once and reuse it. An unbraced one has to be assembled and parsed on every single evaluation.
set a 7
puts "braced: [time {expr {$a * 2}} 20000]"
puts "unbraced: [time {expr $a * 2} 20000]"braced: 0.15119375 microseconds per iteration
unbraced: 1.4540375 microseconds per iterationRoughly ten times, on one machine with one interpreter version. Treat the ratio as indicative rather than exact — and note that it is the least important of the three reasons.
time runs a script a given number of times and reports the average. It is a useful command to know; Module 14 uses it again.
7. When You Genuinely Have an Expression in a Variable
Occasionally you really do want to evaluate an expression that was built at run time. Say so explicitly rather than relying on the unbraced form:
set formula "3 + 4"
puts "result: [expr $formula]"result: 7That works, and here it is intentional. But apply the test from Chapter 2.11: did you write that text, or did it come from outside? If it came from a file, a user, or another program, evaluating it is the same risk as §5 and you should not.
Almost always the better design is to pass the values and keep the expression in your code:
set a 3
set b 4
puts "result: [expr {$a + $b}]"result: 78. The Same Rule Applies to Conditions
if and while evaluate their condition as an expression, so the rule carries over:
if {$count > 0} { ... } ;# braced — correct
if "$count > 0" { ... } ;# unbraced — same three problemsModule 8 covers if, but the habit is formed here: anything that will be read as an expression gets braces.
9. Common Mistakes
Leaving the braces off because it "works". It works until the data changes. §3.
Thinking this is only about speed. It is mostly about correctness and safety. §6 is last for a reason.
Bracing the whole command instead of the expression. {expr $a * 2} is a braced script, not a braced expression. The braces go around the expression: expr {$a * 2}.
Evaluating expression text that came from outside. §7.
Quoting instead of bracing. expr "$a * 2" has exactly the same problems as no braces — quotes allow substitution. Chapter 2.9 is the general version of this.
10. Exercises
Exercise 1 — produce the wrong answer. Set a to "2 + 3" and evaluate expr $a * 2. Confirm you get 8. Then write out, in your own words, the text expr actually parsed.
Exercise 2 — get the honest error. Run the same thing braced. Explain why an error is a better outcome than 8.
Exercise 3 — see the injection. Run the §5 example and confirm INJECTED appears. Then run the braced version and confirm it does not. Explain the difference in one sentence.
Exercise 4 — measure it. Run the §6 timing on your own machine. What ratio do you get? Does the ratio change if you raise the iteration count to 100,000?
Exercise 5 — fix four lines. Each of these is wrong or risky. Rewrite each one:
set total [expr $a + $b]
if "$n > 10" { puts "big" }
set avg [expr "$sum / $count"]
set r [expr $userSuppliedFormula]Exercise 6 — the quoting version. Predict what expr "$a * 2" does when a is "2 + 3", then check. Which of the three problems does it have?
11. Remember
- Always brace the expression:
expr {...}. - Unbraced means your data is evaluated twice — once by Tcl, once by
expr. - That produces wrong answers.
expr $a * 2withaholding2 + 3gives 8, because precedence applies to the assembled text. - Braced gives an honest error instead, which tells you your data was not what you assumed.
- Unbraced will run commands that arrived inside a value. Ordinary data containing a bracket is enough.
- It is also about ten times slower — the least important reason.
- Quoting is not a substitute for bracing. Quotes substitute too.
- The same rule applies to
ifandwhileconditions.
Next: a command that adds one without needing an expression at all.
Where this fits
Part of the Tcl curriculum.
