Tcl Part I · Module 11 · Scope and Namespaces
global: Reaching the Top-Level Scope
The previous chapter established that a procedure's frame contains only what the procedure put there. global is the first and simplest way to add something: it takes a name and makes it refer to the variable of that name in the global scope, for the rest of the call. The behaviour is straightforward once you hold on to one fact, which is that global is an ordinary command executed at run time rather than a declaration the parser notices. That explains why it works on variables that do not exist yet, why it can appear anywhere in a body, and why it does nothing at all at the top level.
Foundation10 min readTclPart IScopeglobalVariables
Part I · Module 11 · Chapter 11.2 · global: Reaching the Top-Level Scope
1. The command
set counter 0
proc bump {} {
global counter
incr counter
}
bump
bump
bump
puts "counter is $counter"counter is 3global counter says: for the rest of this call, the local name counter refers to the global variable counter. Everything after it — reading, writing, incr, info exists — operates on the global.
Several names in one command:
set a 1
set b 2
proc both {} {
global a b
return "$a $b"
}
puts [both]1 22. It is a link, not a copy
Writing through the declared name changes the global:
set shared 10
proc readit {} { global shared ; return $shared }
proc writeit {} { global shared ; set shared 99 }
puts "before : [readit]"
writeit
puts "after : [readit] (and the global itself is $shared)"before : 10
after : 99 (and the global itself is 99)There is one variable. The procedure is not given a value to work with; it is given access to the variable itself.
3. The declaration creates the link, at the moment it runs
global is a command, not syntax. It executes when control reaches it, which has three consequences.
It works on a variable that does not exist yet. The link is to the name, and assigning through it creates the global:
puts "before : [info exists ::fresh]"
proc makeit {} {
global fresh
set fresh "created"
}
makeit
puts "after : [info exists ::fresh] -> $::fresh"before : 0
after : 1 -> createdIt must come before the name is used locally. If a local of that name already exists, the declaration is refused:
set target "global value"
proc confused {} {
set target "local value" ;# creates a local
global target ;# too late
return $target
}
puts [confused]variable "target" already existsTcl will not quietly rebind a name that is already in use. Put the declaration first and it works, writing through the link:
proc correct {} {
global target
set target "written through the link"
return $target
}
puts [correct]
puts "the global is now: $target"written through the link
the global is now: written through the linkPut global at the top of the procedure, always. That is not only a readability convention — as the two errors above show, anywhere else is either refused outright or means the same name refers to two different variables in one body. Declaring twice is harmless, so there is no cost to being thorough.
At the top level it does nothing, as Chapter 11.1 §4 showed, because there is no local scope to link from.
4. Diagnosing a missing declaration
The failure mode depends on whether the body reads the name before writing it — the asymmetry from Chapter 11.1 §7.
Reads first: a clear error.
set total 0
proc read_broken {} {
return [expr {$total + 1}]
}
read_brokencan't read "total": no such variableWrites first: complete silence.
set count 0
proc bump_broken {} {
incr count
}
bump_broken
bump_broken
puts "count is still $count"count is still 0incr created a fresh local on each call and discarded it. No error, no effect.
5. The :: alternative
A name qualified with a leading :: refers to the global scope directly, with no declaration:
set mode "fast"
proc show {} {
return $::mode
}
puts [show]
proc change {} {
set ::mode "slow"
}
change
puts $modefast
slowWhich to use:
global name | $::name | |
|---|---|---|
| Declaration needed | yes, once | no |
| Reads in the body | $name | $::name |
| Best for | several uses in one procedure | a single use |
| Makes the dependency visible | yes, in one line at the top | only at each use |
6. Legitimate uses
global has a bad reputation, mostly deserved — Chapter 11.3 makes that case. These uses are not controversial:
Tcl's own globals, which you have no choice about:
proc platform_name {} {
global tcl_platform
return $tcl_platform(platform)
}
puts [platform_name]unixConfiguration assembled once at start-up and then only read:
set ::config [dict create mode fast retries 3]
proc retries {} {
return [dict get $::config retries]
}
puts [retries]3A single, clearly-named piece of program state such as a verbosity flag — where the alternative is threading the same argument through every procedure in the call chain.
The common thread is that these are set rarely and read often. The trouble starts when many procedures write to the same global, which is the subject of the next chapter.
7. A worked example
A run counter with its state declared once at the top of each procedure that uses it:
set ::runs 0
set ::failures 0
proc record {ok} {
global runs failures
incr runs
if {!$ok} { incr failures }
}
proc summary {} {
global runs failures
if {$runs == 0} { return "no runs" }
set rate [expr {100.0 * ($runs - $failures) / $runs}]
return [format "%d runs, %d failed, %.1f%% pass" $runs $failures $rate]
}
foreach result {1 1 0 1 0} { record $result }
puts [summary]5 runs, 2 failed, 60.0% passBoth procedures declare what they touch on their first line, so a reader knows the full extent of the shared state without reading the bodies.
8. Exercises
Exercise 1. What does global counter do, and when does it take effect?
Exercise 2. Write a procedure that increments a global named hits, and call it twice.
Exercise 3. What happens here, and why?
set v "outer"
proc f {} {
set v "inner"
global v
return $v
}
puts [f]Exercise 4. Why does a procedure missing a global sometimes error and sometimes do nothing?
Exercise 5. Give one case where $::name is preferable to global name, and one where the reverse is true.
Exercise 6. A counter procedure runs without error and the counter never moves. What is the first thing to check?
Answers
1. It links the local name counter to the global variable of that name for the rest of the call. It takes effect at the moment the command runs, not when the procedure is defined — so anything before it in the body refers to a local.
2.
set hits 0
proc hit {} {
global hits
incr hits
}
hit
hit
puts $hits23. It fails with variable "v" already exists. The set v "inner" created a local called v, and global will not rebind a name that is already in use in the frame. Moving global v above the set fixes it — and then the set writes through to the global.
4. Because the two cases fail differently. If the body reads the name before assigning to it, there is no such local and Tcl raises can't read "...": no such variable. If the body only writes — set, incr, lappend, all of which create a missing variable — a local is created, used, and discarded, so nothing is reported and nothing changes.
5. $::name is preferable for a single reference, where a declaration line would be more text than the use. global name is preferable when the procedure uses the variable several times, or when you want every global it depends on visible in one line at the top.
6. A missing global declaration. That is the only failure here that is completely silent — the procedure is incrementing a fresh local on each call and throwing it away.
9. Remember
global name ...links one or more local names to the global variables of the same names, for the rest of the call.- It is a link, not a copy — writing through the name changes the global.
- It is a command executed at run time, so it works on variables that do not exist yet, and it only takes effect from the point it runs.
- Put it at the top of the procedure, or the same name means two different variables in one body.
- At the top level it does nothing, because there is no local scope to link from.
- A missing
globalerrors only if the body reads before writing — otherwise it fails completely silently. $::nameis the no-declaration alternative, better for a single use;globaldocuments the dependency in one visible line.- Uncontroversial uses: Tcl's own globals, read-only configuration, and one clearly-named flag.
Where this fits
Part of the Tcl curriculum.
