Skip to content
VLSI Mentor

Tcl Part I · Module 7 · Dictionaries and Arrays

Two Ways to Hold Key-Value Data

Lists got you a long way, and they run out the moment a record grows past two or three fields — nobody wants to remember that position 4 is the timeout. Tcl answers that with two structures, not one, and most tutorials introduce them as interchangeable alternatives. They are not. One of them is a value, like a number or a string. The other is not a value at all; it is a family of variables sharing a name. This chapter teaches that single difference first, because it is what decides, in every later chapter, which structure your code is actually allowed to use.

Foundation11 min readTclPart IDictionariesArraysData structures

Part I · Module 7 · Chapter 7.1 · Two Ways to Hold Key-Value Data

1. The problem both structures solve

Module 6 ended with lassign, which turns a positional record into named variables:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
lassign $row name width direction

That works, and it has a ceiling. The list still has to be built in exactly the right order, every producer and every consumer has to agree on that order, and adding a field in the middle quietly breaks everything downstream. Chapter 6.14 made that argument and then deferred the fix. This is the fix.

What you want is to store a value under a name rather than at a position:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
# instead of: position 2 is the width
# you want:   the key "width" is the width

Tcl gives you two ways to do that. Both are correct Tcl. They behave completely differently.

2. The two forms, side by side

Here is the same data, held both ways:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
# Form one — a dictionary
set cfg [dict create mode fast runs 3]

# Form two — an array
array set opt {mode fast runs 3}

They look parallel. Read the next two lines and they stop looking parallel:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
puts $cfg
# mode fast runs 3

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

The first line printed. The second one is an error — and not a subtle one. cfg is a variable holding a value, so $cfg reads it. opt is not a variable at all. There is no such thing as "the value of opt".

3. What an array actually is

When you write this:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
array set opt {mode fast runs 3}

Tcl creates two variables. Their names are opt(mode) and opt(runs). The parentheses are part of the name, the same way the letters are. opt on its own is a label for the family, not a member of it.

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
puts $opt(mode)
puts $opt(runs)
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
fast
3

Those two lines work because each one names a real variable. This is why $opt fails: you asked Tcl to read a variable whose name is opt, and no such variable exists.

4. What a dictionary actually is

A dictionary is a value — in exactly the sense Chapter 3.1 used. You can prove it:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
set cfg [dict create mode fast runs 3]
puts [expr {$cfg eq "mode fast runs 3"}]
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
1

The dictionary is the string mode fast runs 3. Not a wrapper around it, not something that prints like it — it is equal to it. A dict is a flat sequence of alternating keys and values, read by dictionary rules, in the same way a list is a string read by list rules.

So the familiar list commands still see it as text:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
puts [llength $cfg]
puts [dict size $cfg]
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
4
2

Four words; two pairs. Same value, two readings — the same pattern Chapter 6.1 opened with.

5. The consequence that decides everything

Because a dict is a value and an array is not, they behave differently at every boundary where values move. A procedure boundary is the clearest one.

Two parallel left-to-right flows. One dict is a single value, created by dict create, and it passes cleanly into and out of a procedure. One array is two separate variables, created by array set, and it will not pass; upvar is needed instead.One dicta single valuedict createmode fast runs 3Passes cleanlyit is one valueOne arraytwo variablesarray seta(mode), a(runs)Will not passupvar instead12
Figure 1 — the one difference that matters. A dictionary is a single value: it goes into a procedure as an argument, comes back as a return value, and nests inside other structures without ceremony. An array is a family of separate variables bound to one scope; it cannot be passed or returned as itself, and reaching one from another procedure requires upvar to link to the caller's name.

6. Proving it at a procedure boundary

A dict passes like any other argument:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
proc summarise {d} {
    return "[dict get $d mode] with [dict get $d runs] runs"
}

set cfg [dict create mode fast runs 3]
puts [summarise $cfg]
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
fast with 3 runs

Nothing special happened there. $cfg is a value; values are what arguments are.

Now try the same thing with an array:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
array set opt {mode fast runs 3}

proc summarise2 {a} {
    return "got: $a"
}

puts [summarise2 $opt]
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
can't read "opt": variable is array

The call never even reached the procedure. $opt failed while Tcl was assembling the arguments — exactly as it did in section 2. There is no value to hand over.

The way to give a procedure access to an array is to pass its name and link to it:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
proc summarise3 {name} {
    upvar 1 $name a
    return "[set a(mode)] with [set a(runs)] runs"
}

puts [summarise3 opt]
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
fast with 3 runs

Notice what changed: you passed opt, not $opt. The procedure received a name and used upvar to bind it to a local alias. That works, and it is a different style of programming — the procedure now reaches into its caller's scope rather than receiving data.

7. Returning one

The same asymmetry runs the other way. A procedure can build a dict and return it:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
proc make_config {} {
    return [dict create mode fast runs 3]
}

set cfg [make_config]
puts [dict get $cfg mode]
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
fast

A procedure cannot return an array, because there is nothing to return. The closest it can do is flatten the array into a list and hand that back:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
proc make_opts {} {
    array set x {mode fast runs 3}
    return [array get x]
}

puts [make_opts]
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
runs 3 mode fast

Read that output carefully: it is the same flat key-value text a dict is. Flattening an array produces something a dict can consume. That is the bridge, and Chapter 7.12 is built on it.

One detail to file away: the pairs came back as runs then mode, which is not the order they were written in. That is not a mistake and not a one-off — an array simply does not keep an order. Chapter 7.10 shows how to impose one, and Chapter 7.12 shows what it costs when you convert.

8. Nesting

One more consequence, and it is the one that bites hardest in real scripts. Because a dict is a value, you can put dicts inside lists and inside other dicts:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
set rows [list [dict create n u1 w 8] [dict create n u2 w 16]]

foreach r $rows {
    puts "[dict get $r n] is [dict get $r w] bits"
}
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
u1 is 8 bits
u2 is 16 bits

A list of dictionaries. Completely ordinary. There is no equivalent for arrays — you cannot put an array inside a list, because an array is not a thing that can be inside anything. Chapter 7.6 builds on this properly.

9. So which one should you use?

The rule, stated once and applied for the rest of the module:

Use a dictionary when the data is something you pass, return, or store inside another structure. Use an array when you want variable-style access to a lot of named values inside a single scope — or when existing code hands you one.

In new code, the dictionary is the default. Reach for an array when you have a positive reason:

SituationReach for
A procedure takes or returns the dataDictionary
The data goes inside a list, or inside another recordDictionary
You want insertion order preservedDictionary
Counting things in one procedure with incr opt(x)Array
Tcl itself gives you one (env, tcl_platform)Array — no choice
Existing code in your project uses themArray — match the code

That last row is not a small matter. Arrays predate dictionaries by well over a decade, and a great deal of working Tcl is written with them. Chapter 7.8 onwards teaches them properly, not as a historical curiosity.

10. Exercises

Exercise 1. Without running it, say which of these two lines errors and what the message will be:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
set a [dict create x 1] ; puts $a
array set b {x 1}       ; puts $b

Exercise 2. Write a procedure widest that takes a list of dictionaries, each with keys n and w, and returns the n of the one with the largest w.

Exercise 3. Explain in one sentence why summarise3 opt (section 6) passes opt rather than $opt.

Exercise 4. A colleague's procedure ends with return [array get results]. What does the caller receive, and what are the two things it can do with it?

Exercise 5. Show that a dictionary is a string by printing the result of comparing one with eq against the text you expect.

Exercise 6. For each of these, say dict or array and give your reason in half a sentence: (a) a count of pass and fail results tallied inside one procedure; (b) a record describing one module, returned by a procedure; (c) the environment variables Tcl gives you; (d) a list of twenty such records.

Answers

1. The first prints x 1. The second errors with can't read "b": variable is array — b is not a variable, only b(x) is.

2.

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
proc widest {rows} {
    set best ""
    set bestw -1
    foreach r $rows {
        if {[dict get $r w] > $bestw} {
            set bestw [dict get $r w]
            set best [dict get $r n]
        }
    }
    return $best
}

puts [widest [list [dict create n u1 w 8] [dict create n u2 w 16]]]
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
u2

3. Because $opt is an error — the procedure needs the array's name so that upvar can link to it, not its value, which does not exist.

4. A flat list of alternating keys and values. It can read it as a dictionary directly, or load it into an array of its own with array set.

5.

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
set d [dict create mode fast runs 3]
puts [expr {$d eq "mode fast runs 3"}]
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
1

6. (a) array — local tallying with incr is exactly what arrays are good at; (b) dict — it is returned, so it must be a value; (c) array — env is one, you have no choice; (d) dict — only values can go inside a list.

11. Remember

  • A dictionary is a value. dict create returns one; $cfg reads it; it is literally equal to its own flat text.
  • An array is a family of variables. array set creates them; $opt alone is an error — can't read "opt": variable is array.
  • Only values cross procedure boundaries. A dict passes and returns normally; an array needs upvar and a name.
  • Only values nest. Dicts go inside lists and inside dicts. Arrays go nowhere.
  • array get flattens an array into exactly the shape a dict is — that is the bridge between them.
  • Default to a dictionary in new code; use an array for scope-local tallying, or when Tcl or existing code hands you one.
  • llength and dict size read the same value differently — four words, two pairs.

Where this fits

Part of the Tcl curriculum.