:TITLE: Variable scope - global and upvar ;# ;# RCSID: $Header: /cvsroot/tcl/tcltutorial/original/Tcl13.lsn,v 1.1 2004/11/04 16:01:13 davidw Exp $ ;# Copyright (c) 1995 Clif Flynt ;# 9300 Fleming Rd. ;# Dexter, MI 48130 ;# clif@cflynt.com ;# See file "NOTICE" for licensing terms. ;# :LESSON_TEXT_START_LEVEL 0: Tcl evaluates a variable name within one of two scopes: the local scope within a proc, and a global scope (the code and variables outside of any proc). Like C, Tcl defines only one global space.

The scope in which a variable will be evaluated can be changed with the global or upvar command.

The global command will cause a variable in a local scope to be evaluated in the global scope instead.

The upvar command behaves similarly in that it changes a variables scope, but differently in that it can map the variable to a new name. Upvar 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.

For example, this code will link the variable y in the proc to the variable z in the global scope:

proc p {x} {
  upvar $x y
  puts "$y"
  }
set z "A"
p z

The syntax for upvar is:

upvar ?level? otherVar1 myVar1 ?otherVar2 myVar2 ...?

Upvar 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 preceeded 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.

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. :TEXT_END: :LESSON_TEXT_START_LEVEL 1: Tcl evaluates a variable name within one of two scopes: the local scope within a proc, and a global scope (the code and variables outside of any proc). Like C, Tcl defines only one global space.

The scope in which a variable will be evaluated can be changed with the global or upvar command.

The global command will cause a variable in a local scope to be evaluated in the global scope instead.

The upvar command behaves similarly. Upvar 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.

The syntax for upvar is:

upvar ?level? otherVar1 myVar1 ?otherVar2 myVar2 ...?

Upvar 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 preceeded 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.

My personal opinion is that using upvar with anything except #0 or 1 is asking for trouble.

The use of global is hard to avoid, but you should avoid having too many global variables. If you start needing lots of globals, you may want to look at your design again.

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.

:TEXT_END: :LESSON_TEXT_START_LEVEL 2: Tcl evaluates a variable name within one of two scopes: the local scope within a proc, and a global scope (the code and variables outside of any proc).

The local scope means that you can have variables in your proc with the same name as variables in the calling proc (or mainline code) without the values of one variable stepping on the value of the other. for instance:

proc ShortLoop {} {
  for{set i 0} {$i < 10} {incr i} { ... do stuff ..}
  }

...
for {set i 0} {$i <  11} {incr i} { ShortLoop }
This code would never exit if the i in shortloop were the same as the i in the mainline code. Every time shortloop was called, it would set i back to 10.

Sometimes, though, you need access to a variable in a different scope than the current one. For instance, when you have a proc that needs to change the value of an argument you need this facility. It is also much more convenient to keep one copy of a state variable that all code can reference than to keep passing copies of that variable around.

The scope in which a variable will be evaluated can be changed with the global or upvar command.

The global command will cause a variable in a local scope to be evaluated in the global scope instead. In the previous example, if there were a line

global i

in shortloop before the loop, then i would be global and the loop would never exit.

The upvar command behaves similarly. Upvar 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.

The syntax for upvar is:

upvar ?level? othervar1 myvar1 ?othervar2 myvar2 ...?

Upvar 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. The default, 1, references the procedure that called this procedure, while a 2 would reference the procedure that called the procedure that called this procedure, etc.

If the level number is preceeded 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. In this case, a proc doesn't need to know how many levels down it has been called to reference a variable up several levels.

Another digression on programming style.

My personal opinion is that using upvar with anything except 0 or 1 is asking for trouble. A proc with upvar 2 .. in it has too much intimate knowlege of code that is too far away from it, and you will get bitten someday when you try to use that proc in other code, or need to modify the code two levels up. Even something as simple as changing a variable name could cause working code to break.

The use of global is hard to avoid, but you should avoid having too many global variables. If you start needing lots of globals, you may want to look at your design again. It is recommended that you start all globals in a package with a common prefix to separate them from similar globals that may exist in other packages you will need to incorporate later. Instead of a global variable name like itemCount use TestApp_itemCount. This will help avoid unexpected name clashes.

The other trick to avoid having too many globla variables is to use the associative arrays. This lets you use one global variable, like TestAppState and indices like itemCount, dataList The best solution is to also use namespaces, covered in future lessons. :TEXT_END: :CODE_START: ;# An example of Upvar ;# Convert a value to a positive number before assigning. 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" ;# nesting Upvars ;# A second level proc - This will be called by one 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; } ;# A first level proc - This will be called by the global space code. 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" :TEXT_END: