Variable scope - global and upvar
Tcl evaluates variables within a scope delineated by
procs, namespaces (see Building reusable
libraries - packages and namespaces), and at the topmost level,
the global
scope.
The scope in which a variable will be evaluated can be changed
with the global
and upvar
commands.
The global
command will cause a
variable in a local scope (inside a procedure) to refer to the
global variable of that name.
The upvar
command is similar. It
"ties" the name of a variable in the current scope to a variable in
a different scope. This is commonly used to simulate
pass-by-reference to procs.
You might also encounter the variable
command in others' Tcl code. It is part
of the namespace system and is discussed in detail in that
chapter.
Normally, Tcl uses a type of "garbage collection" called
reference counting in order to automatically clean up variables
when they are not used anymore, such as when they go "out of scope"
at the end of a procedure, so that you don't have to keep track of
them yourself. It is also possible to explicitly unset them with
the aptly named unset
command.
The syntax for upvar
is:
upvar
?level?
otherVar1
myVar1
?otherVar2
myVar2?
...
?otherVarN
myVarN?
The upvar
command causes myVar1
to become a reference to otherVar1
, and myVar2
to
become a reference to otherVar2
, etc. The
otherVar
variable is declared to be at
level
relative to the current procedure.
By default level
is 1, the next level
up.
If a number is used for the level
, then
level references that many levels up the stack from the current
level.
If the level
number is preceded by
a #
symbol, then it references that
many levels down from the global scope. If level
is #0
, then the
reference is to a variable at the global level.
If you are using upvar with anything except #0 or 1, you are most likely asking for trouble, unless you really know what you're doing.
You should avoid using global variables if possible. If you have a lot of globals, you should reconsider the design of your program.
Note that since there is only one global space it is surprisingly easy to have name conflicts if you are importing other peoples code and aren't careful. It is recommended that you start global variables with an identifiable prefix to help avoid unexpected conflicts.
Example
proc SetPositive {variable value } { upvar $variable myvar if {$value < 0} { set myvar [expr {-$value}] } else { set myvar $value } return $myvar } SetPositive x 5 SetPositive y -5 puts "X : $x Y: $y\n" proc two {y} { upvar 1 $y z ;# tie the calling value to variable z upvar 2 x a ;# Tie variable x two levels up to a puts "two: Z: $z A: $a" ;# Output the values, just to confirm set z 1 ;# Set z, the passed variable to 1; set a 2 ;# Set x, two layers up to 2; } proc one {y} { upvar $y z ;# This ties the calling value to variable z puts "one: Z: $z" ;# Output that value, to check it is 5 two z ;# call proc two, which will change the value } one y ;# Call one, and output X and Y after the call. puts "\nX: $x Y: $y" proc existence {variable} { upvar $variable testVar if { [info exists testVar] } { puts "$variable Exists" } else { puts "$variable Does Not Exist" } } set x 1 set y 2 for {set i 0} {$i < 5} {incr i} { set a($i) $i; } puts "\ntesting unsetting a simple variable" # Confirm that x exists. existence x # Unset x unset x puts "x has been unset" # Confirm that x no longer exists. existence x