Tcl Part I · Module 7 · Dictionaries and Arrays
Dictionaries: dict set and dict get
This is the chapter that changes how you store things. A dictionary pairs each value with a name you choose, so a record stops being a sequence you have to count through and becomes something you read. The commands are small and there are only two you need to start. One of them, though, takes a variable name while the other takes a value — and when you mix those up, Tcl does not stop you, does not warn you, and quietly puts your data somewhere you will never look. That trap is in here, demonstrated, because finding it yourself at midnight is worse.
Foundation11 min readTclPart IDictionariesdict setdict get
Part I · Module 7 · Chapter 7.2 · Dictionaries: dict set and dict get
1. Two ways to make one
dict create takes alternating keys and values and returns a dictionary:
set cfg [dict create mode fast runs 3]
puts $cfgmode fast runs 3dict set builds one a pair at a time, into a variable:
dict set cfg2 mode fast
dict set cfg2 runs 3
puts $cfg2mode fast runs 3Both produce the same value. Note that cfg2 did not have to exist first — dict set created it. That is convenient and it is also how a typo becomes a new dictionary instead of an error, which is worth remembering.
2. Reading a value back
set cfg [dict create mode fast runs 3 width 8]
puts [dict get $cfg mode]
puts [dict get $cfg width]fast
8dict get takes the dictionary and the key, and returns the value. That is the whole command.
3. Updating, and where the key ends up
Setting a key that already exists replaces its value:
set cfg [dict create mode fast runs 3]
dict set cfg runs 10
puts $cfgmode fast runs 10Setting a key that does not exist appends it at the end:
dict set cfg width 8
puts $cfgmode fast runs 10 width 8Dictionaries remember insertion order. That is a real guarantee you can rely on, and it is one of the places where dictionaries and arrays differ sharply — Chapter 7.10 shows an array doing the opposite.
Order survives rewriting, too. An updated key stays where it was rather than moving to the end:
set d [dict create]
foreach k {zebra apple mango} { dict set d $k 1 }
puts [dict keys $d]
dict set d zebra 99
puts [dict keys $d]zebra apple mango
zebra apple mangozebra kept its position. Only genuinely new keys go on the end.
4. When the key is not there
dict get on a missing key is an error, not an empty string:
set cfg [dict create mode fast]
puts [dict get $cfg runs]key "runs" not known in dictionaryThis is deliberate, and it is different from what Chapter 6.4 taught about lindex, which returns an empty string for an out-of-range index. A dictionary key is something you named on purpose, so asking for one that is not there is treated as a mistake worth stopping for.
Handling that properly is the whole of Chapter 7.3.
5. Keys and values can be anything
A key is just a string, so it can contain spaces, punctuation, or digits — as long as you group it so that it stays one word:
set d [dict create]
dict set d "max fanout" 16
dict set d 42 answer
puts $d
puts [dict get $d "max fanout"]{max fanout} 16 42 answer
16Look at the stored form: {max fanout} came back braced. That is the same automatic quoting Chapter 6.2 showed for list — a dictionary is a value with list structure, so it quotes whatever it must to keep the structure readable.
Values work the same way:
dict set d note "two words here"
puts $d{max fanout} 16 42 answer note {two words here}6. A dictionary is a value — with all that implies
Section 4 of Chapter 7.1 proved a dict is literally its own flat text. Two practical consequences follow immediately.
First, you can print one, store one in a file, and read it straight back:
set cfg [dict create mode fast runs 3]
set line $cfg
puts [dict get $line mode]fastNo serialisation step. The text form is the dictionary.
Second, an odd number of words is not a dictionary, and Tcl says so the moment you try to use it as one:
puts [dict get "a 1 b" a]missing value to go with keyThat is the dictionary equivalent of unmatched open brace in list from Chapter 6.3: the text is fine as text, and it does not satisfy the rules this command reads by.
7. The asymmetry that fails silently
This is the one to internalise.
dict set cfg mode fast ;# takes a VARIABLE NAME — cfg
dict get $cfg mode ;# takes a VALUE — $cfgdict set modifies a variable, so it needs the name, exactly like lappend and lset in Chapter 6.6 and Chapter 6.8. dict get just reads a value, so it takes the value, exactly like llength and lindex.
Now watch what happens when you write $cfg where cfg belongs:
set cfg [dict create mode fast runs 3]
dict set $cfg width 8
puts "cfg is still: $cfg"cfg is still: mode fast runs 3No error. The update vanished. Where did it go?
puts [set {mode fast runs 3}]width 8Tcl did exactly what you asked. $cfg substituted to the text mode fast runs 3, dict set took that text as a variable name, and created a variable with that name holding a one-pair dictionary. Your actual configuration was never touched.
8. A worked example
Collect a few settings, then report on them:
set run [dict create]
dict set run name smoke
dict set run width 8
dict set run cycles 2000
dict set run mode fast
puts "name : [dict get $run name]"
puts "width : [dict get $run width]"
puts "cycles : [dict get $run cycles]"
puts "stored : $run"name : smoke
width : 8
cycles : 2000
stored : name smoke width 8 cycles 2000 mode fastThen change one thing and pass the whole record to a procedure:
dict set run cycles 5000
proc describe {r} {
return "[dict get $r name]: [dict get $r cycles] cycles at width [dict get $r width]"
}
puts [describe $run]smoke: 5000 cycles at width 8The procedure received the entire record as one argument. That is the payoff from Chapter 7.1, in use.
9. Exercises
Exercise 1. What does this print, and why is it not an error?
dict set brandnew key value
puts $brandnewExercise 2. Build a dictionary describing a port with keys name, dir and width, then print a single line like clk is an in port, 1 bit(s).
Exercise 3. Predict the output before running it:
set d [dict create a 1 b 2]
dict set d a 99
dict set d c 3
puts [dict keys $d]Exercise 4. Explain, in one sentence each, why dict set takes cfg and dict get takes $cfg.
Exercise 5. The following runs without error and does not do what it looks like. Say what it actually does.
set cfg [dict create a 1]
dict set $cfg b 2Exercise 6. Write a procedure bump that takes a dictionary and a key, returns a new dictionary with that key's value increased by one, and leaves the caller's dictionary unchanged. Prove it is unchanged.
Answers
1. It prints key value. dict set creates the variable if it does not exist, so there is nothing to complain about — which is also why a mistyped variable name silently becomes a brand-new dictionary.
2.
set p [dict create name clk dir in width 1]
puts "[dict get $p name] is an [dict get $p dir] port, [dict get $p width] bit(s)"clk is an in port, 1 bit(s)3. a b c. Rewriting a leaves it in place; only the new key c goes on the end.
4. dict set modifies the variable, so it needs the name in order to find it. dict get only reads, so it needs nothing but the value itself.
5. $cfg becomes the text a 1, so dict set creates a variable named a 1 holding the dictionary b 2. cfg is unchanged, and no error is reported.
6.
proc bump {d key} {
dict set d $key [expr {[dict get $d $key] + 1}]
return $d
}
set counts [dict create hits 4]
set newer [bump $counts hits]
puts "returned: $newer"
puts "original: $counts"returned: hits 5
original: hits 4The procedure's d is its own copy, so modifying it cannot affect the caller's variable — the same value semantics Chapter 6.6 relied on.
10. Remember
dict create k v ...returns a dictionary;dict set name k vbuilds one into a variable and creates that variable if needed.dict get $d keyreturns the value, and errors withkey "..." not known in dictionaryif there is none.- That error is deliberate — unlike
lindex, which returns an empty string out of range. - Dictionaries preserve insertion order, and rewriting a key keeps it in place.
- Keys and values are just strings, automatically quoted when they contain spaces. Keep keys boring anyway.
- An odd number of words is not a dictionary —
missing value to go with key. dict settakes a name;dict gettakes a value. Writingdict set $cfg ...creates a variable named after your data and silently loses the update.- Modifying commands take the bare name; reading commands take
$name.
Where this fits
Part of the Tcl curriculum.
