Tcl Part I · Module 8 · Conditions and Control Flow
Why the Condition and the Body Both Need Braces
Every Tcl style guide tells you to brace the condition of an if. Very few show you what happens if you do not, which is a shame, because the demonstrations are short and completely convincing — and because one of them is not a style problem at all but a way for data to execute as code. This chapter works through four concrete failures, each with the exact message Tcl produces, and then states the rule in a form you can apply without thinking about it again. If you read one chapter of this module carefully, make it this one.
Foundation12 min readTclPart IControl flowBracesQuoting
Part I · Module 8 · Chapter 8.2 · Why the Condition and the Body Both Need Braces
1. The rule first
Brace the condition. Brace the body. Always, with no exceptions worth remembering.
if {$a == 1} { puts yes } ;# correct
if $a == 1 { puts yes } ;# broken — see the rest of this chapterThe rest of this chapter is why. It is worth the ten minutes because the failures are not all loud.
2. What actually happens without braces
Recall from Chapter 2.11 that Tcl substitutes variables when it parses the line, before the command runs. Braces prevent that substitution (Chapter 2.8).
So with braces, if receives the literal text $a == 1 and hands it to expr, which looks a up itself at the moment of evaluation. Without braces, $a is replaced first, and if receives whatever the variable happened to contain — as separate words.
3. Failure one: an ordinary value
Start with the case that looks like it should obviously work:
set a 1
if $a == 1 { puts yes }wrong # args: extra words after "else" clause in "if" command$a became 1, so if received the words 1, ==, 1, {puts yes}. It took 1 as the whole condition, == as the body, and then found two more words it had no use for. The message mentions else because that is where if ran out of things it recognises.
Note there is no else anywhere in that line. The error appears for the simplest possible if.
4. Failure two: an empty value
set a ""
if $a == 1 { puts yes }empty expression
in expression ""A different message, because this time $a vanished entirely and the condition if received was nothing at all. An empty configuration value, a field that was never filled in, a command that returned nothing — all produce this, and all produce it only at the moment the data happens to be empty.
With braces, the same empty value is handled correctly:
set a ""
if {$a == 1} { puts yes } else { puts no }no5. Failure three: a value with a space
set name "two words"
if $name eq "two words" { puts yes }invalid bareword "two"
in expression "two words";
should be "$two" or "{two}" or "two(...)" or ...$name expanded into two words, so the condition became two and everything after it drifted one place. Tcl's message is genuinely helpful here — it is telling you that it found the bare word two where it expected a value, and suggesting what you might have meant.
Braced, it simply works:
set name "two words"
if {$name eq "two words"} { puts yes } else { puts no }yes6. Failure four: the one that is not an error
The three above are loud. This one is not.
set danger {[puts SIDEEFFECT ; expr 1]}
if $danger { puts "the branch ran" }SIDEEFFECT
the branch ranRead that output. The string stored in danger contained command substitution, and leaving the condition unbraced executed it. puts SIDEEFFECT ran. Then expr 1 supplied a true value, so the body ran too.
With braces, Tcl refuses:
set danger {[puts SIDEEFFECT ; expr 1]}
if {$danger} { puts "the branch ran" }expected boolean value but got "[puts SIDEEFFECT ; expr 1]"Nothing executed. expr looked at the value, saw text that is not a boolean, and said so.
7. And it is slower
There is one unbraced form that does run: quoting the whole condition so it stays a single word.
set a 1
if "$a == 1" { puts yes }yesThat works, and it has every problem from sections 3 to 6 — the value is still substituted before expr sees it, so an empty value, a space, or a bracket breaks it in exactly the same ways. It is also slow, for the reason Chapter 3.8 established: a braced expression is compiled once, while a quoted one is rebuilt and re-parsed on every evaluation.
set a 1
puts "braced : [time {if {$a == 1} {}} 50000]"
puts "quoted : [time {if "$a == 1" {}} 50000]"braced : 0.2742 microseconds per iteration
quoted : 2.5168 microseconds per iterationThe braced figure is stable across runs on this machine. The quoted one is not — repeated measurements put it between roughly eight and twenty-eight times slower, depending on what else the machine was doing. The exact multiple is not worth quoting; the direction never changed in any run.
This is the least important reason to brace — sections 3 to 6 already settled it — but it does mean there is nothing to trade away. The correct form is also the fast one.
8. The body: braces versus quotes
A quoted body is legal and runs:
set x 1
if {$x == 1} "puts quoted-ok"quoted-okSo why brace it? Because a quoted body is substituted once, when the line is parsed, and then the result is executed. Anything that looks like substitution inside it is resolved at the wrong moment:
set msg "hello"
if {$x == 1} "puts $msg"helloThat worked — and it worked by baking the current value of msg into the script before running it. Put something less friendly in the variable and the script you execute is not the one you wrote. With braces, the body is handed over untouched and $msg is looked up when the body actually runs:
set msg {it is [exit 1]}
if {$x == 1} {puts $msg}it is [exit 1]The brackets printed as text. The interpreter is still running — a quoted body would have tried to execute them.
9. A checklist you can apply mechanically
if {CONDITION} {
BODY
} elseif {CONDITION} {
BODY
} else {
BODY
}- Every condition is inside
{ }. - Every body is inside
{ }. - The opening brace of a body is on the same line as its keyword — Chapter 2.10 explains why putting it on the next line produces
wrong # args: no script following .... elseifis one word.
If you write it that way every time, none of the four failures in this chapter can happen to you.
10. Exercises
Exercise 1. For each, give the exact error message:
set a 1 ; if $a == 1 {puts yes}
set a "" ; if $a == 1 {puts yes}Exercise 2. Why does the first of those mention else when there is no else in the line?
Exercise 3. What does this print, and what is alarming about it?
set v {[puts GOTCHA ; expr 0]}
if $v {puts body} else {puts no-body}Exercise 4. Explain the difference in when $msg is looked up between if {1} "puts $msg" and if {1} {puts $msg}.
Exercise 5. A script reads a value from a file into $line and tests it with if $line eq "done" {...}. Give two different inputs that break it, with different symptoms.
Exercise 6. The braced condition measured about 0.27 µs and the quoted one several times that, varying between runs. Why is this the weakest of the arguments for bracing?
Answers
1.
wrong # args: extra words after "else" clause in "if" commandempty expression
in expression ""2. if scans its arguments looking for the shapes it knows — condition, body, optionally elseif/else and more. After taking 1 as the condition and == as the body, the next word is neither elseif nor else, so it has run off the end of the grammar it understands and reports the leftover words at the last place they could have belonged.
3. It prints GOTCHA and then no-body. The alarming part is the first line: the unbraced condition executed puts GOTCHA out of what was supposed to be data. That expr 0 then made the condition false is incidental — the code already ran.
4. With quotes, $msg is substituted while the line is being parsed, so the body that gets executed already contains the text of msg baked in. With braces, the body is passed through untouched and $msg is looked up when the body runs — which is later, and in the right variable context.
5. A line that is empty gives empty expression / in expression "". A line containing a space, say all done, gives invalid bareword "all". A third, worse one: a line containing [...] would execute it.
6. Because correctness and safety have already decided the question. A performance argument would matter only if the fast form were otherwise worse — and here the fast form is also the only correct one, so the measurement changes no decision.
11. Remember
- Brace the condition and brace the body, every time.
- Without braces, Tcl expands the variable while parsing, so
ifreceives the value's words rather than an expression. - An ordinary value gives
wrong # args: extra words after "else" clause— even with noelsepresent. - An empty value gives
empty expression; a value with a space givesinvalid bareword. - An unbraced condition executes command substitution found in the data. That is a code-execution path through a comparison.
- Braces stop all of it:
expected boolean value but got "..."instead of running anything. - A quoted body is substituted once, at parse time, so
$and[ ]inside it resolve at the wrong moment. - Braced is also several times faster — the only unbraced form that runs is the quoted one, and it is re-parsed every evaluation. Least important reason; it makes the choice free.
- Opening brace on the same line as its keyword, and
elseifas one word.
Where this fits
Part of the Tcl curriculum.
