Skip to content
VLSI Mentor

Tcl Part I · Module 8 · Conditions and Control Flow

Fallthrough and the default Branch

Two small features that finish off switch. The first lets several patterns share one body, written with a single dash, which is Tcl's deliberate replacement for the accidental fallthrough other languages are famous for. The second is default, which you have seen in every example so far and which has a rule nobody mentions: it only behaves as a catch-all when it is the last pattern in the list. Put it anywhere else and it silently stops being special, matching only the literal word. This chapter proves that, and then argues that a silent default is usually the wrong choice anyway.

Foundation10 min readTclPart IControl flowswitchdefault

Part I · Module 8 · Chapter 8.6 · Fallthrough and the default Branch

1. Sharing a body between patterns

A body of - means "use the next pattern's body":

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
proc severity {level} {
    switch -- $level {
        error -
        warn    { return "problem" }
        info -
        debug   { return "noise" }
        default { return "unknown" }
    }
}

foreach l {error warn info debug other} {
    puts [format "%-6s %s" $l [severity $l]]
}
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
error  problem
warn   problem
info   noise
debug  noise
other  unknown

error has no body of its own, so it uses warn's. The chain can be any length:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
switch -- c {
    a - b - c - d { puts "one of the first four" }
    e            { puts "e" }
}
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
one of the first four

2. This is the only fallthrough there is

Chapter 8.4 §2 established that Tcl never falls from one body into the next by accident. Writing two full bodies gives you two independent cases:

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

Only ran a. In C, forgetting a break here would print both. Tcl removes that class of bug entirely and gives you - for the cases where sharing really is what you want.

3. A dangling dash is an error

The last pattern must have a real body:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
switch -- x {
    a { puts one }
    x -
}
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
no body specified for pattern "x"

Useful, because the mistake is easy to make when deleting a case from the middle of a list.

4. default, the ordinary way

As the last pattern, default matches anything that got that far:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
switch -- zz {
    a       { puts "A" }
    b       { puts "B" }
    default { puts "fell through to default" }
}
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
fell through to default

5. default is only special when it is last

Now the rule that is not in most tutorials. Move default anywhere other than the final position and it stops being a catch-all:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
switch -- zz {
    default { puts "D" }
    a       { puts "A" }
}
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet

Nothing printed. And in the middle:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
switch -- zz {
    a       { puts "A" }
    default { puts "D" }
    b       { puts "B" }
}
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet

Also nothing. Compare those with the working version from section 4, which is identical except for where default sits.

So what is it doing instead? Matching literally:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
switch -- default {
    default { puts "D" }
    a       { puts "A" }
}
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
D

The value was the actual word default, and the pattern default matched it as an ordinary string. That pair of results pins the behaviour down exactly: in a non-final position, default is not a fallback at all — it is the eight characters d-e-f-a-u-l-t.

6. default should be a decision

default has three sensible shapes, and picking by reflex is how bugs survive.

A real case, when "anything else" has a genuine meaning:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
proc runs_for {mode} {
    switch -- $mode {
        fast    { return 1 }
        full    { return 10 }
        default { return 5 }
    }
}
puts [runs_for fast]
puts [runs_for anything-else]
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
1
5

A deliberate error, when anything else means something upstream is wrong:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
proc strict_runs {mode} {
    switch -- $mode {
        fast    { return 1 }
        full    { return 10 }
        default { error "unknown mode: '$mode'" }
    }
}
puts [strict_runs full]
if {[catch {strict_runs bogus} msg]} { puts "rejected: $msg" }
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
10
rejected: unknown mode: 'bogus'

Explicitly nothing, when doing nothing is correct and you want to say so:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
switch -- $level {
    error   { incr problems }
    warn    { incr problems }
    default { }
}

An empty body documents the decision. Leaving default out altogether looks the same to the interpreter and tells a reader nothing — they cannot know whether you considered the case or forgot it.

7. A worked example

A log counter using fallthrough for the grouping and an erroring default for the unexpected:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
proc tally {lines} {
    set counts [dict create problems 0 noise 0]
    foreach l $lines {
        switch -- $l {
            FATAL -
            ERROR -
            WARN    { dict incr counts problems }
            INFO -
            DEBUG   { dict incr counts noise }
            default { error "unrecognised level: '$l'" }
        }
    }
    return $counts
}

puts [tally {INFO WARN ERROR DEBUG WARN}]

if {[catch {tally {INFO TRACE}} msg]} {
    puts "rejected: $msg"
}
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
problems 3 noise 2
rejected: unrecognised level: 'TRACE'

Five patterns, two bodies, one error path. Adding a new level means adding one line in the right group — and forgetting to classify a level is caught immediately rather than being quietly counted as nothing.

8. Exercises

Exercise 1. What does this print for each of a, b and c?

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
switch -- $x {
    a - b   { puts "ab" }
    c       { puts "c" }
    default { puts "other" }
}

Exercise 2. What is the error from switch -- x { a {puts one} x - }?

Exercise 3. Predict the output of each, and explain the difference:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
switch -- zz { a {puts A} default {puts D} }
switch -- zz { default {puts D} a {puts A} }

Exercise 4. Why does switch -- default { default {puts D} a {puts A} } print D?

Exercise 5. Write a switch mapping r/red, g/green and b/blue to colour names, erroring on anything else.

Exercise 6. A colleague adds a new case to the bottom of a working switch, below the existing default, and now unmatched values silently do nothing. Explain what happened and give the fix.

Answers

1. a and b both print ab — a has no body of its own so it uses b's. c prints c. Anything else prints other.

2. no body specified for pattern "x". A trailing - has nothing to fall through to.

3. The first prints D; the second prints nothing. default is a catch-all only as the final pattern. In the second, it is in first position and so is treated as the literal string default, which zz does not match — and nothing else matches either.

4. Because in a non-final position default is matched literally, and here the value really is the word default. The pattern and the value are the same eight characters.

5.

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
proc colour {c} {
    switch -- $c {
        r - red   { return "red" }
        g - green { return "green" }
        b - blue  { return "blue" }
        default   { error "unknown colour: '$c'" }
    }
}
foreach c {r green b} { puts [colour $c] }
if {[catch {colour x} m]} { puts "rejected: $m" }
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
red
green
blue
rejected: unknown colour: 'x'

6. default is no longer the last pattern, so it stopped acting as a catch-all and became a literal string that nothing matches. The fix is to move the new case above default so that default is last again.

9. Remember

  • A body of - means "use the next pattern's body". Chains of any length work: a - b - c { ... }.
  • Read - as "and also". It is a property of the pattern list, not a jump.
  • Tcl never falls through implicitly — - is the only mechanism, so C's missing-break bug does not exist here.
  • A trailing - with nothing after it gives no body specified for pattern "x".
  • default is a catch-all only when it is the last pattern. Anywhere else it matches the literal string default and everything unmatched silently does nothing.
  • When adding a case to an existing switch, add it above the default.
  • Make default a decision: a real case, a deliberate error, or an explicitly empty body that says you thought about it.
  • Prefer erroring for dispatch on user-supplied or configured values — a named failure beats a silent default.

Where this fits

Part of the Tcl curriculum.