When the Tcl intepreter enters a procedure written in Tcl, it creates a special table of variables local to that procedure while its code is being executed. This table can contain both "real" local variables and special "links" to other variables. Such links are indistinguishable from "real" variables as long as Tcl commands (such as set
, unset
etc) are concerned.
These links are created by the upvar
command, and it's able to create a link to any variable on any stack frame (including the global scope—frame 0).
Since Tcl is highly dynamic, its variables can come and go any time and so a variable linked to by upvar
might not exist at the time a link to it is created, observe:
% unset foo
can't unset "foo": no such variable
% proc test name { upvar 1 $name v; set v bar }
% test foo
bar
% set foo
bar
Note that I first demonstrate that the variable named "foo" does not exist, then set it in a procedure which uses upvar
(and the variable is autocreated) and then demonstrate that the variable exists after the procedure has quit.
Also note that upvar
is not about accessing global variables—this is usually achieved using the global
and variable
commands; instead, upvar
is used to work with variables rather than values. This is usually needed when we need to change something "in place"; one of the better examples of this is the lappend
command which accepts the name of a variable containing a list and appends one or more elements to that list, changing it in place. To achieve this, we pass lappend
the name of a variable, not just a list value itself. Now compare this with the linsert
command which accepts a value, not a variable, so it takes a list and produces another list.
Another thing to note is that by default (in its two-argument form), upvar
links to a variable with the specified name one level up the stack, not to a global variable. I mean, you can do this:
proc foo {name value} {
upvar $name v
set v $value
}
proc bar {} {
set x ""
foo x test
puts $x ;# will print "test"
}
In this example, the procedure "foo" changes the variable local to procedure "bar".
Hence, to make the intent more clear, many people prefer to always specify the number of stack frames upvar
should "climb up", like in upvar 1 $varName v
which is the same as upvar $varName v
but clearer.
Another useful application of this is referring to local variables, by specifying zero stack levels to climb up—this trick is sometimes useful to more conveniently access variables in arrays:
proc foo {} {
set a(some_long_name) test
upvar 0 a(some_long_name) v
puts $v ;# prints "test"
upvar a(some_even_more_long_name) x
if {![info exists x]} {
set x bar
}
}
As a bonus, note that upvar
also understands absolute numbers of stack frames which are specified using the "#" prefix, and "#0" means the global scope. That way you could bind to a global variable while the procedure in your original example only would bind to global variables if executed in the global scope.