Skip to content
VLSI Mentor

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

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
set counter 0

proc bump {} {
    global counter
    incr counter
}

bump
bump
bump
puts "counter is $counter"
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
counter is 3

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

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
set a 1
set b 2

proc both {} {
    global a b
    return "$a $b"
}
puts [both]
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
1 2

Writing through the declared name changes the global:

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

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:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
puts "before : [info exists ::fresh]"

proc makeit {} {
    global fresh
    set fresh "created"
}
makeit

puts "after  : [info exists ::fresh] -> $::fresh"
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
before : 0
after  : 1 -> created

It must come before the name is used locally. If a local of that name already exists, the declaration is refused:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
set target "global value"

proc confused {} {
    set target "local value"      ;# creates a local
    global target                 ;# too late
    return $target
}

puts [confused]
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
variable "target" already exists

Tcl will not quietly rebind a name that is already in use. Put the declaration first and it works, writing through the link:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
proc correct {} {
    global target
    set target "written through the link"
    return $target
}

puts [correct]
puts "the global is now: $target"
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
written through the link
the global is now: written through the link

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

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
set total 0
proc read_broken {} {
    return [expr {$total + 1}]
}
read_broken
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
can't read "total": no such variable

Writes first: complete silence.

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
set count 0
proc bump_broken {} {
    incr count
}
bump_broken
bump_broken
puts "count is still $count"
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
count is still 0

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

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
set mode "fast"

proc show {} {
    return $::mode
}
puts [show]

proc change {} {
    set ::mode "slow"
}
change
puts $mode
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
fast
slow

Which to use:

global name$::name
Declaration neededyes, onceno
Reads in the body$name$::name
Best forseveral uses in one procedurea single use
Makes the dependency visibleyes, in one line at the toponly 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:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
proc platform_name {} {
    global tcl_platform
    return $tcl_platform(platform)
}
puts [platform_name]
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
unix

Configuration assembled once at start-up and then only read:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
set ::config [dict create mode fast retries 3]

proc retries {} {
    return [dict get $::config retries]
}
puts [retries]
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
3

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

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
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]
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
5 runs, 2 failed, 60.0% pass

Both 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?

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

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
set hits 0
proc hit {} {
    global hits
    incr hits
}
hit
hit
puts $hits
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
2

3. 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 global errors only if the body reads before writing — otherwise it fails completely silently.
  • $::name is the no-declaration alternative, better for a single use; global documents 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.