Skip to content
VLSI Mentor

Tcl Part I · Module 8 · Conditions and Control Flow

switch: Multi-Way Branching

When a chain of elseif branches all compare the same variable against a different constant, there is a command that says exactly that and reads as a table instead of a staircase. switch is that command, and it is mostly as simple as it looks. Two things about it are not: the choice between writing the cases inside one pair of braces or as loose arguments changes which branch runs, not just how it looks; and a line starting with a hash inside those braces is not a comment, which produces one of the most confusing error messages in Tcl. Both are in here.

Foundation11 min readTclPart IControl flowswitchDispatch

Part I · Module 8 · Chapter 8.4 · switch: Multi-Way Branching

1. The shape

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
set mode full

switch -- $mode {
    fast    { set runs 1 }
    full    { set runs 10 }
    deep    { set runs 100 }
    default { set runs 0 }
}

puts "runs = $runs"
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
runs = 10

One value, a list of pattern–body pairs, and the first matching body runs. Compare it with the elseif chain it replaces:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
if {$mode eq "fast"} {
    set runs 1
} elseif {$mode eq "full"} {
    set runs 10
} elseif {$mode eq "deep"} {
    set runs 100
} else {
    set runs 0
}

Same behaviour. The switch version states once that everything is comparing $mode, and the cases line up as a table you can read down.

2. Only one body runs

Unlike C, Tcl's switch does not fall through from one case into the next by accident. Exactly one body runs — the first that matches:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
switch -- b {
    a { puts "ran a" }
    b { puts "ran b" }
    c { puts "ran c" }
}
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
ran b

There is a deliberate way to share one body between several patterns, and it is explicit. Chapter 8.6 covers it.

3. No match is not an error

If nothing matches and there is no default, switch quietly does nothing:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
switch -- zzz {
    a { puts "ran a" }
    b { puts "ran b" }
}
puts "still here"
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
still here

That silence is the single most common source of "my script did nothing and said nothing". Supplying a default is how you decide what should happen instead — again, Chapter 8.6.

4. switch returns a value

Like if, switch is a command and returns the result of the body that ran:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
set runs [switch -- full {
    fast    { expr 1 }
    full    { expr 10 }
    default { expr 0 }
}]
puts $runs
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
10

Which makes the table form read very well for a pure lookup:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
proc runs_for {mode} {
    return [switch -- $mode {
        fast    { expr 1 }
        full    { expr 10 }
        deep    { expr 100 }
        default { expr 0 }
    }]
}

foreach m {fast full deep other} {
    puts [format "%-6s %d" $m [runs_for $m]]
}
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
fast   1
full   10
deep   100
other  0

When nothing matches and there is no default, the result is the empty string.

5. The braced body list, and why it matters

There are two ways to write the cases. Inside one pair of braces:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
switch -- $mode {
    fast { ... }
    full { ... }
}

Or as loose arguments:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
switch -- $mode fast { ... } full { ... }

They are not equivalent. The braced form is one word, so nothing inside it is substituted before switch sees it — the rule from Chapter 2.8. The loose form is several words, each substituted as the line is parsed.

Watch which arm runs:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
set pat fast

puts [switch -- fast {
    $pat { expr {"matched-the-variable"} }
    fast { expr {"matched-the-literal"} }
}]
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
matched-the-literal
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
puts [switch -- fast $pat { expr {"matched-the-variable"} } fast { expr {"matched-the-literal"} }]
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
matched-the-variable

In the braced form the first pattern is the three literal characters $pat, which does not match fast, so the second arm wins. In the loose form $pat was replaced with fast before switch ran, so the first arm wins.

6. Bodies are scripts, not expressions

Each body is an ordinary script, so it can be as long as you like and can contain anything:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
set level warn
set problems 0

switch -- $level {
    error {
        incr problems
        puts "ERROR seen"
    }
    warn {
        incr problems
        puts "warning seen"
        puts "continuing anyway"
    }
    info {
        puts "nothing to do"
    }
}

puts "problems = $problems"
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
warning seen
continuing anyway
problems = 1

7. A # inside a switch is not a comment

This is the trap. Comments in Tcl are only recognised where a command is expected (Chapter 1.8), and the inside of a braced body list is not a command position — it is a flat list of patterns and bodies.

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
switch -- a {
    # this looks like a comment
    a       { puts "hit" }
    default { puts "miss" }
}
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
invalid command name "comment"

To see why, read that brace block the way Tcl does — as a list of ten words, paired off:

PairPatternBody
1#this
2lookslike
3acomment
4aputs "hit"
5defaultputs "miss"

The words of your comment became patterns and bodies. The value being switched on is a, and the first pattern equal to a is pair 3, whose body is the single word comment — so Tcl ran comment as a command. Your intended puts "hit" sat one pair further down and was never reached.

Change the comment's wording and the bug changes shape. With an odd number of words in the block, Tcl notices something is wrong and tells you exactly what it suspects:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
switch -- a {
    # pick the right branch
    a       { puts "hit" }
    default { puts "miss" }
}
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
extra switch pattern with no body, this may be due to a comment incorrectly placed outside of a switch body - see the "switch" documentation

That message exists specifically because this mistake is so common. You only get it when the word count happens to be odd — which is why the first version, with its silent mis-pairing, is the more dangerous of the two.

8. A worked example

Dispatching on a report type, with each body doing real work:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
proc summarise {kind rows} {
    switch -- $kind {
        count {
            return "[llength $rows] rows"
        }
        first {
            if {[llength $rows] == 0} { return "no rows" }
            return "first: [lindex $rows 0]"
        }
        widths {
            set out {}
            foreach r $rows { lappend out [string length $r] }
            return "widths: $out"
        }
        default {
            return "unknown report kind: $kind"
        }
    }
}

set rows {clk reset data_in}
foreach k {count first widths bogus} {
    puts [format "%-7s %s" $k [summarise $k $rows]]
}
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
count   3 rows
first   first: clk
widths  widths: 3 5 7
bogus   unknown report kind: bogus

Four cases, each self-contained, and a default that names the problem rather than returning nothing.

9. Exercises

Exercise 1. Rewrite this as a switch:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
if {$c eq "r"} { set n red } elseif {$c eq "g"} { set n green } elseif {$c eq "b"} { set n blue } else { set n unknown }

Exercise 2. What does this print, and why is that dangerous?

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
switch -- zzz { a { puts A } b { puts B } }
puts done

Exercise 3. Explain why these two give different answers:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
set p fast
switch -- fast { $p {puts VAR} fast {puts LIT} }
switch -- fast $p {puts VAR} fast {puts LIT}

Exercise 4. What is wrong with this, and what is the fix?

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
switch -- $mode {
    # fast is the default
    fast { set n 1 }
    full { set n 10 }
}

Exercise 5. Write a switch that returns a value, and show what it returns when nothing matches and there is no default.

Exercise 6. Give one case where an elseif chain is better than a switch, and say what makes it so.

Answers

1.

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
switch -- $c {
    r       { set n red }
    g       { set n green }
    b       { set n blue }
    default { set n unknown }
}

2. It prints only done. Nothing matched, there is no default, and switch does nothing silently — so a typo'd or unexpected value produces no output and no error at all.

3. The first is a braced body list, so it is a single word and $p is never substituted — the pattern is the literal text $p, which does not match fast, so LIT runs. The second is loose arguments, so $p is substituted to fast while the line is parsed and the first arm matches, printing VAR.

4. The # line is not a comment. Inside a braced body list Tcl is reading patterns and bodies, so #, fast, is, the and default all become patterns or bodies and the pairing shifts. Move the comment above the switch:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
# fast is the default
switch -- $mode {
    fast { set n 1 }
    full { set n 10 }
}

5.

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
set r [switch -- zzz {
    a { expr 1 }
    b { expr 2 }
}]
puts "'[set r]'"
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
''

The empty string.

6. When the branches test different things, or test ranges rather than equality — if {$n < 0} ... elseif {$n < 10} ... cannot be written as a switch on one value. switch is for one value against many constants or patterns; elseif is for genuinely different questions.

10. Remember

  • switch -- $value { pattern body ... } runs the first matching body, and only that one.
  • Always write -- so a value beginning with a dash is not mistaken for an option.
  • No match and no default does nothing, silently — the commonest cause of a script that produces no output and no error.
  • switch returns the body's value, or the empty string when nothing ran.
  • Use the braced body list. It keeps patterns literal; the loose-argument form substitutes them first and can run a different arm.
  • Bodies are full scripts, not expressions.
  • A # line inside a braced body list is not a comment — its words become patterns and bodies and one gets executed. Put comments above the switch or inside a body.
  • Use switch for one value against many constants; use elseif when the branches ask different questions.

Where this fits

Part of the Tcl curriculum.