Skip to content
VLSI Mentor

Tcl Part I · Module 8 · Conditions and Control Flow

Testing for a Value That Is Missing

There are at least three ways for a value to be absent in Tcl, and they mean different things: the variable was never set at all, it was set and holds the empty string, or it holds a list with nothing in it. A fourth case looks like absence and is not — a value of zero. Programmers arriving from other languages reach for one condition to cover all of them, and in Tcl that condition does not merely give the wrong answer; in three of the four cases it raises an error. This closing chapter of the module lays out each state, gives the right test for each, and ends on an empty-looking value that is not empty at all.

Foundation11 min readTclPart IControl flowEmpty valuesinfo exists

Part I · Module 8 · Chapter 8.7 · Testing for a Value That Is Missing

1. Four states, not one

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
# 1 — never set
unset -nocomplain a

# 2 — set to the empty string
set b ""

# 3 — set to zero
set c 0

# 4 — set to an empty list
set d {}

These are genuinely different. A configuration field that was never supplied is not the same as one the user deliberately blanked, and neither is the same as one set to 0. A careful script distinguishes them; the trouble is that the obvious test does not.

2. What if {$x} actually does

Try the one condition on each state:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
unset -nocomplain a
if {$a} { puts yes }
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
can't read "a": no such variable
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
set b ""
if {$b} { puts yes }
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
expected boolean value but got ""
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
set c 0
if {$c} { puts yes } else { puts no }
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
no
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
set e "hello"
if {$e} { puts yes }
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
expected boolean value but got "hello"

Collected:

Stateif {$x}
never seterror — can't read "x": no such variable
set to ""error — expected boolean value but got ""
set to 0false
set to texterror — expected boolean value but got "hello"

It is useful in exactly one of the four, and that one is the case that is not about absence at all. if {$x} is a test for "x is a boolean and it is true" — nothing more. It was never a presence test.

3. The right test for each state

Was it ever set?

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
unset -nocomplain name
puts [info exists name]
set name ""
puts [info exists name]
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
0
1

info exists is the only safe test for a variable that might not exist — and note that setting it to the empty string makes it exist. The two questions really are separate.

Is it empty?

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
set name ""
puts [expr {$name eq ""}]
puts [string length $name]
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
1
0

eq compares as text and never guesses, which is why it is right here and == is not — the rule from Chapter 4.4.

Is the list empty?

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
set items {}
puts [llength $items]
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
0

Is it zero?

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
set c 0
puts [expr {$c == 0}]
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
1

A summary:

QuestionTest
Does the variable exist?info exists name
Is the string empty?$name eq ""
Is the list empty?[llength $items] == 0
Is the number zero?$n == 0
Is the dictionary empty?[dict size $d] == 0
Is the array empty?[array size a] == 0

4. Order matters when you combine them

Testing existence and emptiness together needs the existence test first, because reading a variable that does not exist is an error:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
unset -nocomplain name

if {[info exists name] && $name ne ""} {
    puts "has a value"
} else {
    puts "missing or empty"
}
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
missing or empty

No error, because && short-circuits — the rule from Chapter 4.5 and the one Chapter 8.3 §5 warned you not to reorder. Swap the two tests and it breaks:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
unset -nocomplain name
if {$name ne "" && [info exists name]} { puts "has a value" }
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
can't read "name": no such variable

5. Giving a default safely

The common job is "use it if it is there, otherwise use a default":

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
proc setting {name default} {
    upvar 1 $name v
    if {![info exists v]} { return $default }
    if {$v eq ""}        { return $default }
    return $v
}

unset -nocomplain timeout
puts [setting timeout 60]

set timeout ""
puts [setting timeout 60]

set timeout 300
puts [setting timeout 60]

set timeout 0
puts [setting timeout 60]
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
60
60
300
0

Note the last line. 0 is a real value and survives, which is exactly the behaviour you want and exactly what a truthiness-based test would destroy — it would have silently replaced a deliberate zero with 60.

6. The same question for dictionaries and arrays

Module 7 covered the structure-specific versions, and they follow the same shape:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
set d [dict create blank "" mode fast]

puts "key exists      : [dict exists $d blank]"
puts "value is empty  : [expr {[dict get $d blank] eq ""}]"
puts "key absent      : [dict exists $d nothere]"
puts "dict is empty   : [expr {[dict size $d] == 0}]"
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
key exists      : 1
value is empty  : 1
key absent      : 0
dict is empty   : 0

A key that exists with an empty value is present. Testing presence by comparing the value to "" conflates the two — the point from Chapter 7.3 §5, in its general form.

7. An empty-looking value that is not empty

One last case, and it catches people who think they have this sorted.

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
set l [list ""]

puts "llength      : [llength $l]"
puts "eq to empty  : [expr {$l eq ""}]"
puts "its text     : '$l'"
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
llength      : 1
eq to empty  : 0
its text     : '{}'

That list contains one element, and that element is the empty string. Its text form is {} — two characters that look like emptiness and are not. So the two tests disagree:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
set l [list ""]
if {[llength $l] == 0} { puts "llength says empty" } else { puts "llength says 1 element" }
if {$l eq ""}          { puts "eq says empty" }      else { puts "eq says not empty" }
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
llength says 1 element
eq says not empty

Both are right about their own question. The lesson is that "empty" is not one property — a list with one empty element is an empty string in none of these senses and an empty list in none of them either, and which one you meant has to be decided.

8. What Module 8 covered

Seven chapters on making a script decide:

  • if, elseif and else — and if as a command that takes script arguments and returns a value
  • Why the condition and the body are both braced, demonstrated through four concrete failures including one that executes data as code
  • Nesting, and the two transformations that flatten it safely — plus when flattening is wrong
  • switch as a table, and the braced body list that keeps patterns literal
  • The three matching modes, and what -- really protects
  • Fallthrough with -, and default as a deliberate decision
  • Four kinds of nothing, and the right test for each

The thread is the one Chapter 8.1 opened with: these are commands, not syntax. They take arguments, those arguments get substituted according to the ordinary rules, and almost every surprise in the module comes from substitution happening at a different moment than you assumed — the unbraced condition, the quoted body, the loose switch arm, the comment that was not a comment.

9. What comes next

Module 9 is Loops and Iteration, starting with foreach because it is the idiomatic Tcl loop — one list, several lists at once, several variables per pass — then for, while, break and continue, and accumulating results.

It also carries this module's brace rule into a place where getting it wrong is louder: a while condition written without braces is evaluated once and never again, which does not error — it simply loops forever.

10. Exercises

Exercise 1. For each state, say what if {$x} does: never set, "", 0, "hello".

Exercise 2. Write the correct test for each: does cfg exist; is $name the empty string; does $items have no elements; is $n zero.

Exercise 3. Why must [info exists name] come first in if {[info exists name] && $name ne ""}?

Exercise 4. What is wrong with using a truthiness-style test to apply a default to a timeout setting?

Exercise 5. Given set l [list ""], what do llength $l and $l eq "" return, and why do they disagree?

Exercise 6. Write require_value which, given a variable name, returns the value, or raises an error naming the variable if it is unset or empty.

Answers

1. Never set: error, can't read "x": no such variable. "": error, expected boolean value but got "". 0: false. "hello": error, expected boolean value but got "hello".

2. info exists cfg; $name eq ""; [llength $items] == 0; $n == 0.

3. Because && short-circuits, so putting the existence test first means $name is only read once it is known to exist. Reversed, Tcl reads a non-existent variable and raises can't read "name": no such variable.

4. It cannot distinguish a deliberate 0 from a missing value, so a timeout explicitly set to zero would be silently replaced by the default. In Tcl it is worse than that: on an unset or empty variable the test raises an error rather than returning false.

5. llength $l is 1 and $l eq "" is 0. They disagree because the list has one element that happens to be the empty string — it is a one-element list, and its text form {} is not the empty string.

6.

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
proc require_value {name} {
    upvar 1 $name v
    if {![info exists v]} { error "'$name' is not set" }
    if {$v eq ""}         { error "'$name' is empty" }
    return $v
}

set target top
puts [require_value target]

set target ""
if {[catch {require_value target} m]} { puts "rejected: $m" }

unset -nocomplain target
if {[catch {require_value target} m]} { puts "rejected: $m" }
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
top
rejected: 'target' is empty
rejected: 'target' is not set

Two different messages, because they are two different problems.

11. Remember

  • "Not set", "set to empty", "set to zero" and "an empty list" are four different states.
  • if {$x} is not a presence test. It errors on an unset variable, errors on "", errors on ordinary text, and is false only for zero.
  • Tcl has no truthiness. A condition must be a boolean; anything else is an error, not false.
  • info exists name is the only safe test for a variable that may not exist.
  • $name eq "" for an empty string, [llength $items] == 0 for an empty list, $n == 0 for zero.
  • Put info exists first when combining with && — reading a missing variable is an error.
  • A default applied by truthiness destroys a deliberate 0.
  • dict exists and dict size are the dictionary forms; a key present with an empty value still exists.
  • [list ""] has length 1 and prints as {} — empty-looking, not empty.

Where this fits

Part of the Tcl curriculum.