upvar(n)
๐ NAME ๐
upvar โ bestie, this is how you create a link to a variable living in a different stack frame. Yes, we're reaching across levels. It's giving โจlong-distance friendshipโจ but for variables.
๐ SYNOPSIS ๐
upvar ?level? otherVar myVar ?otherVar myVar ...?
โจ DESCRIPTION โจ
Okay so here's the tea. This command arranges for one or more local variables in the current procedure to refer to variables in an enclosing procedure call โ or to global variables. Like, you're not copying anything, you're literally making them the same variable. Iconic behaviour honestly. ๐
level can take any of the forms that the uplevel command allows, and you can straight up omit it โ it defaults to 1. Lowkey convenient.
For each otherVar argument, upvar makes the variable by that name in the procedure frame given by level (or at global level, if level is #0) accessible in the current procedure under the name you give in the corresponding myVar argument.
Here's the cute part: the variable named by otherVar doesn't need to exist yet when you call this. It'll get created the first time myVar is referenced, just like any ordinary variable. So no pressure, she'll show up when she's needed. ๐ฆ
๐จ But listen: there must not already exist a variable by the namemyVarat the timeupvaris invoked. That's a hard no.
myVar is always treated as the name of a variable, not an array element. If the name looks like an array element, like a(b), you get an error back. Sorry bestie, that's the rules. ๐
otherVar on the other hand is flexible โ it may refer to a scalar variable, an array, or an array element. And upvar returns an empty string. That's it. That's the whole return value. โจ
๐ธ Why would you even do this ๐ธ
The upvar command simplifies the implementation of call-by-name procedure calling, and it also makes it easier to build new control constructs as Tcl procedures. Very main character of it.
For example, consider the following procedure:
proc add2 name {
upvar $name x
set x [expr {$x + 2}]
}
If add2 is invoked with an argument giving the name of a variable, it adds two to the value of that variable. Obsessed. Now, add2 could have been implemented using uplevel instead of upvar โ but upvar makes it simpler for add2 to access the variable in the caller's procedure frame. Easier access, same slay. ๐ฅ
๐ฎ Namespaces, aka the plot twist ๐ฎ
namespace eval is another way (besides procedure calls) that the Tcl naming context can change. It adds a call frame to the stack to represent the namespace context. Which means: each namespace eval command counts as another call level for uplevel and upvar commands. Not me discovering the stack is deeper than I thought. ๐ญ
So for example, info level 1 will return a list describing a command that is either the outermost procedure call or the outermost namespace eval command. Also, uplevel #0 evaluates a script at top-level in the outermost namespace (the global namespace). Iykyk. ๐
๐ซถ Unsetting an upvar variable ๐ซถ
If an upvar variable gets unset (like x in add2 above, say), the unset operation affects the variable it's linked to, not the upvar variable itself. Plot twist!
There is no way to unset an upvar variable except by exiting the procedure in which it is defined. That's the only exit, bestie. ๐ช
However โ and this is the fun part โ it is possible to retarget an upvar variable by executing another upvar command. Glow-up via reassignment, we love to see it. โจ
๐ฎ TRACES AND UPVAR ๐ฎ
upvar interacts with traces in a straightforward but possibly unexpected manner. Honestly? Kinda shook when I read this one.
If a variable trace is defined on otherVar, that trace will be triggered by actions involving myVar. Makes sense, they're linked. But โ and here's the twist โ the trace procedure will be passed the name of myVar, rather than the name of otherVar.
So the output of the following code will be โlocalVarโ rather than โoriginalVarโ:
proc traceproc { name index op } {
puts $name
}
proc setByUpvar { name value } {
upvar $name localVar
set localVar $value
}
set originalVar 1
trace add variable originalVar write traceproc
setByUpvar originalVar 2
Like. She answers to her local name. ๐
Also, if otherVar refers to an element of an array, then the element name is passed as the second argument to the trace procedure. This may be important information in case of traces set on an entire array. So write that down bestie, it matters. ๐
๐ EXAMPLE ๐
A decr command that works like incr except it subtracts the value from the variable instead of adding it. She's the villain arc version of incr, and honestly? Ate. ๐
proc decr {varName {decrement 1}} {
upvar 1 $varName var
incr var [expr {-$decrement}]
}
๐ SEE ALSO ๐
The whole friend group. Go say hi. ๐
๐ KEYWORDS ๐
context, frame, global, level, namespace, procedure, upvar, variable
And that's the whole page, bestie!! This one came straight out of the Tcl Built-In Commands manual, page upvar(n) โ Tcl's the source, the name's right there in the header, and the footer's just Tcl repping the same page. Mwah. ๐ โจ ๐
๐ฆ where this page comes from
- alpine 3.23: tcl9-doc 9.0.3-r0 ยท TCL
- alpine 3.24: tcl9-doc 9.0.3-r2 ยท TCL
- fedora 43: tcl-doc 1:9.0.2-1.fc43 ยท TCL AND GPL-3.0-or-later WITH Bison-exception-2.2 AND BSD-3-Clause
- fedora 44: tcl-doc 1:9.0.2-1.fc44 ยท TCL AND GPL-3.0-or-later WITH Bison-exception-2.2 AND BSD-3-Clause
- nixos 26.05: tcl 9.0.3
- Copyright (c) 1993 The Regents of the University of California
- Copyright (c) 1994-1997 Sun Microsystems, Inc
- ๐ girlified by deepseek-ai/DeepSeek-V4.1-Flash on : a language model's rewrite of the original, under its terms