Skip to content
VLSI Mentor

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.

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
if {$a == 1} { puts yes }       ;# correct
if $a == 1 { puts yes }         ;# broken — see the rest of this chapter

The 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.

Two parallel left-to-right flows. A braced condition reaches expr as unexpanded text and is compared safely at run time. An unbraced condition is expanded by Tcl before if runs, producing a wrong number of arguments error, or silently running whatever the value contained.Bracedif {$a == 1}expr gets $astill unexpandedWorksany value, safelyUnbracedif $a == 1Tcl expands itbefore if runswrong # argsor worse: it runs12
Figure 1 — where the two forms diverge. A braced condition travels to expr as unexpanded text, so expr looks the variable up itself and compares one value against another. An unbraced condition is expanded by Tcl while the line is still being parsed, so if receives the variable's contents split into words — which may be the wrong number of words, may not be a valid expression, and may contain command substitution that runs.

3. Failure one: an ordinary value

Start with the case that looks like it should obviously work:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
set a 1
if $a == 1 { puts yes }
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
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

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
set a ""
if $a == 1 { puts yes }
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
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:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
set a ""
if {$a == 1} { puts yes } else { puts no }
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
no

5. Failure three: a value with a space

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
set name "two words"
if $name eq "two words" { puts yes }
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
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:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
set name "two words"
if {$name eq "two words"} { puts yes } else { puts no }
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
yes

6. Failure four: the one that is not an error

The three above are loud. This one is not.

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
set danger {[puts SIDEEFFECT ; expr 1]}
if $danger { puts "the branch ran" }
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
SIDEEFFECT
the branch ran

Read 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:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
set danger {[puts SIDEEFFECT ; expr 1]}
if {$danger} { puts "the branch ran" }
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
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.

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
set a 1
if "$a == 1" { puts yes }
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
yes

That 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.

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
set a 1
puts "braced : [time {if {$a == 1} {}} 50000]"
puts "quoted : [time {if "$a == 1" {}} 50000]"
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
braced : 0.2742 microseconds per iteration
quoted : 2.5168 microseconds per iteration

The 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:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
set x 1
if {$x == 1} "puts quoted-ok"
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
quoted-ok

So 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:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
set msg "hello"
if {$x == 1} "puts $msg"
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
hello

That 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:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
set msg {it is [exit 1]}
if {$x == 1} {puts $msg}
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
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

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
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 ....
  • elseif is 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:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
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?

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
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.

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
wrong # args: extra words after "else" clause in "if" command
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
empty 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 if receives the value's words rather than an expression.
  • An ordinary value gives wrong # args: extra words after "else" clause — even with no else present.
  • An empty value gives empty expression; a value with a space gives invalid 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 elseif as one word.

Where this fits

Part of the Tcl curriculum.