Tcl Part I · Module 7 · Dictionaries and Arrays
Arrays: Tcl's Original Associative Structure
Arrays came first. They were Tcl's only way to associate a name with a value for well over a decade before dictionaries arrived, and a very large amount of working Tcl is still written with them — including code Tcl itself gives you, because two of the variables available before your script runs a single line are arrays. This chapter teaches what an array actually is, which is stranger and simpler than it looks: not a container holding values, but a set of ordinary variables whose names happen to contain parentheses. Every surprising thing an array does follows from that.
Foundation12 min readTclPart IArraysVariablesLegacy code
Part I · Module 7 · Chapter 7.8 · Arrays: Tcl's Original Associative Structure
1. Making one
There is no "create an array" command. You just set an element:
set counts(pass) 0
set counts(fail) 0
incr counts(pass)
puts "pass = $counts(pass)"
puts "fail = $counts(fail)"pass = 1
fail = 0Two things to notice. The array appeared the moment you set an element — nothing declared it. And incr worked on counts(pass) exactly as it would on any variable from Chapter 3.9, because counts(pass) is an ordinary variable.
2. The parentheses are part of the name
This is the whole chapter in one sentence. counts(pass) is a variable whose name is the eleven characters counts(pass). The parentheses are not an operator being applied to counts; they are letters in a name, the same as the c and the o.
Which is why this fails:
puts $countscan't read "counts": variable is arrayYou asked for a variable called counts. There isn't one. There is counts(pass) and there is counts(fail), and counts is just the part of the name they share.
3. The index can be computed
The part inside the parentheses is substituted like anything else, so it can be a variable or a command result:
set counts(pass) 7
set which pass
puts $counts($which)
foreach stage {parse build check} {
set timing($stage) 0
}
incr timing(build) 42
puts "build took $timing(build)"7
build took 42That is a genuine strength: you can build element names at run time from data, without any special syntax for it.
4. Reading something that is not there
set limits(retries) 3
puts $limits(timeout)can't read "limits(timeout)": no such element in arrayAn error, like reading any undefined variable — and the message distinguishes a missing element from a missing array. Compare it with the earlier one:
| What you wrote | Message |
|---|---|
$counts where counts is an array | can't read "counts": variable is array |
$limits(timeout), no such element | can't read "limits(timeout)": no such element in array |
$nothing, never set | can't read "nothing": no such variable |
Three different problems, three different messages. Reading them accurately saves real time — the skill from Chapter 1.9.
5. A name is either scalar or array, never both
set s 1
set s(x) 2can't set "s(x)": variable isn't arrayOnce s holds an ordinary value, the name is taken. The reverse is also true — after set counts(pass) 0, plain set counts 5 fails. If you need to reuse the name, unset it first.
6. Testing for one
array exists answers whether a name is an array:
array set limits {retries 3}
set plain 1
puts "limits : [array exists limits]"
puts "plain : [array exists plain]"
puts "never : [array exists never]"limits : 1
plain : 0
never : 0info exists does not distinguish — it is true for an array as well:
puts "info exists limits : [info exists limits]"info exists limits : 1It is still the right command for a single element:
puts "retries: [info exists limits(retries)]"
puts "timeout: [info exists limits(timeout)]"retries: 1
timeout: 07. Why arrays are not optional
The honest reason to learn arrays is not that old code uses them — though it does. It is that Tcl hands you arrays before your script runs:
puts "env is an array : [array exists env]"
puts "tcl_platform is an array : [array exists tcl_platform]"
puts "platform : $tcl_platform(platform)"
puts "PATH set : [info exists env(PATH)]"env is an array : 1
tcl_platform is an array : 1
platform : unix
PATH set : 1env holds the environment variables; tcl_platform describes the interpreter and machine. Both are arrays, because both predate dictionaries. You cannot opt out of understanding arrays and still read the environment.
8. What arrays are genuinely good at
Counting and accumulating in one scope, where the element names come from data:
set results {pass fail pass pass skip fail pass}
foreach r $results {
if {![info exists tally($r)]} { set tally($r) 0 }
incr tally($r)
}
foreach k [lsort [array names tally]] {
puts [format "%-6s %d" $k $tally($k)]
}fail 2
pass 4
skip 1incr tally($r) is the attraction: no dict get, no dict set, no reassignment. Just a variable being incremented, with a name worked out at run time.
In fact incr creates the variable from zero if it is missing — the behaviour from Chapter 3.9 — so the guard is optional here:
set results {pass fail pass}
foreach r $results { incr tally2($r) }
puts [array get tally2]fail 1 pass 2Convenient, and it is also how a typo in $r silently creates a brand-new counter instead of an error. The same trade-off incr always had.
That output is also your first sight of something Chapter 7.10 deals with properly: fail came out before pass, which is neither the order they were counted in nor anything else you chose. An array has no order to report.
9. The limits, stated now
Three, so you are not surprised later:
- An array cannot be passed to or returned from a procedure. Chapter 7.1 proved this; it needs
upvarand a name. - An array cannot go inside a list or a dictionary. It is not a value.
- An array has no order. Chapter 7.10 demonstrates this, and it is more disruptive than it sounds.
None of these makes arrays wrong. They make arrays a scope-local tool, which is exactly how the rest of this module treats them.
10. Exercises
Exercise 1. What are the two errors here, and what is the exact message for each?
set a(x) 1
puts $a
puts $a(y)Exercise 2. Write a loop that counts how many times each word appears in {clk rst clk en clk rst} and prints the counts alphabetically.
Exercise 3. Why does set s 1 ; set s(x) 2 fail, and what would you do if you needed the name s for an array?
Exercise 4. Which of array exists, info exists do you use for each question: (a) is cfg an array? (b) is cfg(width) set? (c) does the name cfg exist at all?
Exercise 5. Explain why set a(two words) 1 fails while set "a(two words)" 1 succeeds, referring to how Tcl splits a command into words.
Exercise 6. Print your interpreter's platform and Tcl version, using one built-in array and one ordinary variable.
Answers
1. puts $a gives can't read "a": variable is array — a is not a variable, only a(x) is. puts $a(y) gives can't read "a(y)": no such element in array.
2.
foreach w {clk rst clk en clk rst} { incr seen($w) }
foreach k [lsort [array names seen]] {
puts "$k $seen($k)"
}clk 3
en 1
rst 23. s is already an ordinary variable, and a name is scalar or array but never both — can't set "s(x)": variable isn't array. Call unset s first, or pick a different name.
4. (a) array exists cfg. (b) info exists cfg(width). (c) info exists cfg — it is true for both scalars and arrays, which is exactly the question being asked here.
5. Tcl splits the command into words at spaces before any command runs, so the first form hands set three words — set, a(two, words) and 1 is a fourth — and set accepts at most two. Quoting makes the whole name one word. This is the word-splitting rule from Chapter 2.3, not an array rule.
6.
puts "platform: $tcl_platform(platform)"
puts "version : $tcl_version"platform: unix
version : 8.5tcl_platform is an array; tcl_version is an ordinary variable.
11. Remember
- An array is a family of variables sharing a name, not a container.
counts(pass)is one variable whose name includes the parentheses. $countson its own is an error —can't read "counts": variable is array.- Elements behave like any scalar:
set,incr,append,info existsall work on them. - The index is substituted, so element names can be built from data at run time.
- An index containing a space needs the whole name grouped — a word-splitting rule, not an array rule.
- A name is scalar or array, never both.
array existsasks "is this an array?";info exists name(idx)asks "is this element set?".info exists nameanswers neither precisely.envandtcl_platformare arrays, so arrays are not avoidable in Tcl.- Arrays do not pass, do not nest, and have no order. They are a scope-local tool.
Where this fits
Part of the Tcl curriculum.
