Skip to content
VLSI Mentor

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:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
set counts(pass) 0
set counts(fail) 0
incr counts(pass)

puts "pass = $counts(pass)"
puts "fail = $counts(fail)"
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
pass = 1
fail = 0

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

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
puts $counts
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
can't read "counts": variable is array

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

The name counts fans out to two real variables, counts of pass and counts of fail. Each element behaves like any scalar, while the bare name counts cannot be read.countsthe array namecounts(pass)a real variablecounts(fail)a real variableincr, set, readall work normallybut not $countsvariable is array12
Figure 1 — one name, several variables. Setting counts of pass and counts of fail creates two independent variables that happen to share a prefix. Each behaves exactly like any scalar: set it, read it, increment it. The shared part of the name is not itself a variable, so reading it on its own is an error rather than a collection.

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:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
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)"
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
7
build took 42

That 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

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
set limits(retries) 3
puts $limits(timeout)
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
can't read "limits(timeout)": no such element in array

An 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 wroteMessage
$counts where counts is an arraycan't read "counts": variable is array
$limits(timeout), no such elementcan't read "limits(timeout)": no such element in array
$nothing, never setcan'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

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
set s 1
set s(x) 2
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
can't set "s(x)": variable isn't array

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

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
array set limits {retries 3}
set plain 1

puts "limits : [array exists limits]"
puts "plain  : [array exists plain]"
puts "never  : [array exists never]"
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
limits : 1
plain  : 0
never  : 0

info exists does not distinguish — it is true for an array as well:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
puts "info exists limits : [info exists limits]"
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
info exists limits : 1

It is still the right command for a single element:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
puts "retries: [info exists limits(retries)]"
puts "timeout: [info exists limits(timeout)]"
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
retries: 1
timeout: 0

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

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
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)]"
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
env is an array          : 1
tcl_platform is an array : 1
platform : unix
PATH set : 1

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

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
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)]
}
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
fail   2
pass   4
skip   1

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

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
set results {pass fail pass}
foreach r $results { incr tally2($r) }
puts [array get tally2]
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
fail 1 pass 2

Convenient, 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 upvar and 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?

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

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
foreach w {clk rst clk en clk rst} { incr seen($w) }
foreach k [lsort [array names seen]] {
    puts "$k $seen($k)"
}
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
clk 3
en 1
rst 2

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

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
puts "platform: $tcl_platform(platform)"
puts "version : $tcl_version"
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
platform: unix
version : 8.5

tcl_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.
  • $counts on its own is an error — can't read "counts": variable is array.
  • Elements behave like any scalar: set, incr, append, info exists all 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 exists asks "is this an array?"; info exists name(idx) asks "is this element set?". info exists name answers neither precisely.
  • env and tcl_platform are 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.