[Andreas Drollinger] 2012-12-09 - This page shows how creating variable aliases in Tcl. While Tcl provides the command ''interp alias'' to create command aliases, Tcl provides at first glance no mechanism to create variable aliases. However, the following code examples show that the Tcl command ''upvar'' cannot refer only variables of a different stack frame (as this is explained by the Tcl documentation), but ''upvar'' can also refer variables inside the same stack frame. This can be considered as a variable alias. To remember, upvar's syntax is: ======tcl upvar ?level? otherVar myVar ?otherVar myVar ...? ====== The following example creates a variable ''v'' which is then referred by the variable ''a''. To refer ''v'' inside the current stack frame, the upvar's stack frame level argument has to be set to ''0'': ======tcl set v 123 upvar 0 v a puts $a ==>123 ====== Referring a variable inside the current namespace via upvar works also perfectly inside a procedure: ======tcl proc p {} { set v 123 upvar 0 v a puts $a } p ==>123 ====== This allows defining for convenience an procedure to create variable alias: ======tcl proc valias {v_orig v_alias} { uplevel 1 "upvar 0 $v_orig $v_alias" } set v 123 valias v a puts $a ==>123 ====== Such a variable alias allows changing the referenced variable and to read the updated value via the alias variable: ======tcl # ... continuation from previous code snippet set v 987 puts $a ==>987 ====== But, also the alias variable can be changed, and the updated value can be read via the referenced variable: ======tcl # ... continuation from previous code snippet set a 876 puts $v ==>876 ====== It's obvious that the original referenced variable cannot be accessed anymore if it is deleted. But the alias variable is also deleted at the same time: ======tcl # ... continuation from previous code snippet unset v puts $v ==>can't read "v": no such variable puts $a ==>can't read "a": no such variable ====== And vice versa: If the alias variable is deleted, also the referenced variable will be deleted: ======tcl set v 123 upvar 0 v a unset a puts $a ==>can't read "a": no such variable puts $v ==>can't read "v": no such variable ====== It is interesting that Tcl remembers the link between the referenced variable and the variable alias. If the referenced variable is restored, also the alias variable is restored automatically. ======tcl # ... continuation from previous code snippet set v 987 puts $a 987 ====== And if the alias variable is restored, also the referenced variable is again available: ======tcl # ... continuation from previous code snippet unset v puts $v ==>can't read "v": no such variable puts $a ==>can't read "a": no such variable set a 159 puts $v 159 ====== *** Arrays *** Upvar allows referring entire arrays, as the following example shows. The array variable ''va'' is created and accessed then via the alias variable ''aa'': ======tcl array set va {a 123 b 234 c 345} upvar #0 va aa array get aa ==>a 123 b 234 c 345 ====== An update of the alias variable ''aa'' will immediately update also the referenced variable: ======tcl # ... continuation from previous code snippet set aa(d) 456 array get va ==>d 456 a 123 b 234 c 345 ====== *** Namespaces *** Upvar allows also creating alias variables that refer variables inside namespaces. The following code snipped defines the two namespaces ''n1'' and ''n2'' that contain each one the variable ''v'': ======tcl namespace eval n1 {variable v n1_123} namespace eval n2 {variable v n2_123} ====== The following lines create the alias variable ''a'' that refers the variable ''v'' of the first namespace: ======tcl # ... continuation from previous code snippet upvar 0 n1::v a puts $a ==>n1_123 ====== The reference can be changed to the variable ''v'' of the second namespace by running another ''upvar'' command: ======tcl # ... continuation from previous code snippet upvar 0 n2::v a puts $a ==>n2_123 ====== Deleting the namespace of the currently referenced variable, the alias variable will also be deleted: ======tcl # ... continuation from previous code snippet namespace delete n2 puts $a ==>can't read "a": no such variable ====== However, in opposite to referenced variables that are deleted, Tcl keeps not anymore the reference information if the referenced variable’s namespace is deleted. After restoring the deleted namespace with its variable, the alias variable is still not anymore available: ======tcl # ... continuation from previous code snippet namespace eval n2 {variable v n2_123} puts $a ==>can't read "a": no such variable ====== *** Procedures and alias variables declared as global *** Be careful if you use procedures that define an alias variable that is declared at the same time as a global variable. The following example defines two namespaces that contain each the variable ''v''. A procedure ''select_namespace'' allows selecting one of the namespace and assigning the variable ''v'' of the selected namespace to the global variable ''a'': ======tcl namespace eval n1 {variable v n1_123} namespace eval n2 {variable v n2_123} proc select_namespace {ns} { global a upvar #0 ${ns}::v ::a puts "$a - $::a" } ====== If the first namespace is selected, ''select_namespace'' displays as this is expected the value of the variable of the first namespace. However, if the second namespace is selected, ''select_namespace'' still prints the variable of the first namespace! ======tcl # ... continuation from previous code snippet select_namespace n1 ==>n1_123 - n1_123 select_namespace n2 ==>n1_123 - n2_123 ====== What is happening? It seems that the Tcl interpreter maps the global variable into the procedure stack level at the moment the global variable is declared. In this case this variable refers to the variable ''n1::v''. Changing the reference to a variable of another namespace is not updating this mapping of the declared variable. To make the reference of a global variable to a variable in a certain namespace inside the context of a procedure working, the reference to the global variable needs to be made first via ''upvar'', using an explicate global namespace prefix ''::'', and the global variable needs then to be declared as global in a second step: ======tcl # ... continuation from previous code snippet namespace eval n1 {variable v n1_123} namespace eval n2 {variable v n2_123} proc select_namespace {ns} { upvar #0 ${ns}::v ::a global a puts "$a - $::a" } select_namespace n1 ==>n1_123 - n1_123 select_namespace n2 ==>n2_123 - n2_123 ====== It is of course also possible to access anyway the global variable always with the global namespace prefix ''::'' inside the procedure: ======tcl # ... continuation from previous code snippet proc select_namespace {ns} { upvar #0 ${ns}::v ::a puts $::a } select_namespace n1 ==>n1_123 select_namespace n2 ==>n2_123 ====== *** Application *** Variable references can be used to extend an application to support multiple contexts, without the need to change the entire application to handle the different datasets of the contexts. The dataset of each context can for example be stored for this inside a dedicated namespace. In the following example there are two of these namespaces, each one contains an array variable that has 1'000'000 elements: ======tcl foreach dsn {0 1} { namespace eval dataset($dsn) { for {set k 0} {$k<1000000} {incr k} { set v($k) [expr {rand()}] } } } ====== A brute force solution to make the dataset's variable available for the application inside the global namespace would be to make simply a copy. However, copying huge amount of data is time consuming (and of course also memory consuming): ======tcl # ... continuation from previous code snippet proc select_dataset {dsn} { array set ::v [array get ::dataset($dsn)::v] } time {select_dataset 0} ==>533581 microseconds per iteration time {select_dataset 1} ==>485014 microseconds per iteration ====== A much more elegant solution is to use variable references that can be created nearly instantaneously: ======tcl # ... continuation from previous code snippet unset v; # Clear the previously crated variable inside the global namespace proc select_dataset {dsn} { upvar #0 dataset($dsn)::v ::v } time {select_dataset 0} ==>14 microseconds per iteration time {select_dataset 1} ==>14 microseconds per iteration ====== ---- See also: * [upvar] <> Tutorial | Command | Example