Skip to content
VLSI Mentor

Tcl Part I · Module 11 · Scope and Namespaces

upvar: Operating on the Caller's Variable

Two earlier chapters stopped mid-sentence and pointed here. Chapter 7.1 showed that an array cannot be handed to a procedure, because an array is not a value, and said the way round it was to pass a name. Chapter 10.8 showed a procedure filling in its caller's variables and deferred the mechanism. That mechanism is upvar: it takes the name of a variable in another frame and makes a local name refer to it, so the procedure reads and writes the caller's variable directly. It is the most powerful tool in this module and the one most worth using sparingly, and this chapter covers both halves of that.

Intermediate12 min readTclPart IScopeupvarArrays

Part I · Module 11 · Chapter 11.4 · upvar: Operating on the Caller's Variable

1. Passing a name instead of a value

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
proc push {listVar value} {
    upvar 1 $listVar l
    lappend l $value
}

set results {}
push results "ok"
push results "fail"

puts $results
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
ok fail

Look at the call: push results "ok" passes results bare, not $results. The procedure is given the name of a variable, and upvar 1 $listVar l makes its local l another name for that variable in the caller's frame. From then on, lappend l appends to the caller's list.

This is the same distinction Chapter 7.1 §6 drew: a value goes in as $x, a name goes in as x.

2. The level argument

upvar ?level? otherName localName ...

  • 1 — the caller's frame. This is what you almost always want, and it is the default if you leave the level out.
  • #0 — the global frame, counting from the bottom rather than relatively.
  • 0 — the procedure's own frame, which makes a local alias for another local.
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
set g "global value"

proc lvl1 {name} { upvar 1 $name v ; return $v }
proc mid  {}     { set local "mid-local" ; return [lvl1 local] }
puts "upvar 1  : [mid]"

proc toglobal {name} { upvar #0 $name v ; return $v }
puts "upvar #0 : [toglobal g]"

proc nolevel {name} { upvar $name v ; return $v }
set q "q value"
puts "default  : [nolevel q]"
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
upvar 1  : mid-local
upvar #0 : global value
default  : q value

Deeper levels count outward. From a procedure two calls down, uplevel's sibling numbering applies to upvar as well — 2 is the caller's caller:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
proc outer_f {} { set mine "outer_f-var" ; middle_f }
proc middle_f {} { return [inner_f] }
proc inner_f {} {
    return [list [uplevel 1 {info exists mine}] [uplevel 2 {info exists mine}]]
}
puts [outer_f]
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
0 1

Level 1 from inner_f is middle_f, which has no mine; level 2 is outer_f, which does.

3. It creates the variable if it is not there

The link is to a name, so the caller's variable need not exist yet:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
proc setter {name val} {
    upvar 1 $name v
    set v $val
}

puts "before : [info exists brandnew]"
setter brandnew 7
puts "after  : $brandnew"
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
before : 0
after  : 7

And testing through the link before anything is written is safe — it reports absence rather than erroring:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
proc peek {name} {
    upvar 1 $name v
    return [info exists v]
}
puts [peek never_set_anywhere]
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
0

4. The same name-clash rule as global

Chapter 11.2 §3 showed global refusing a name already in use. upvar behaves identically:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
set outer_var "the caller's"

proc clash {name} {
    set v "local first"
    upvar 1 $name v
    return $v
}
clash outer_var
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
variable "v" already exists

And, as before, the version that catches people is a parameter:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
proc pclash {v} {
    upvar 1 v v
    return $v
}
pclash hello
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
variable "v" already exists

5. Arrays: the promise from Module 7

Chapter 7.1 established that an array cannot be passed or returned, because it is not a value. upvar is the answer:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
proc fill {arrName} {
    upvar 1 $arrName a
    set a(x) 1
    set a(y) 2
}

fill myarr
puts "elements : [lsort [array names myarr]]"
puts "myarr(x) : $myarr(x)"
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
elements : x y
myarr(x) : 1

Reading one works the same way:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
proc total_of {arrName} {
    upvar 1 $arrName a
    set t 0
    foreach k [array names a] { incr t $a($k) }
    return $t
}

array set counts {pass 4 fail 1 skip 2}
puts [total_of counts]
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
7

This is the only mechanism for it. Every procedure in existing Tcl that takes an array does it this way, which is why recognising upvar 1 $arrName a on sight is worth something.

6. It is a weaker contract than returning a value

Chapter 10.8 §7 made this argument and can now be stated precisely. Compare:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
# returns a value
proc minmax {values} {
    set s [lsort -integer $values]
    return [list [lindex $s 0] [lindex $s end]]
}
lassign [minmax {5 2 9}] lo hi
puts "returned : $lo $hi"

# fills in the caller's variables
proc minmax_out {values loName hiName} {
    upvar 1 $loName lo
    upvar 1 $hiName hi
    set s [lsort -integer $values]
    set lo [lindex $s 0]
    set hi [lindex $s end]
}
minmax_out {5 2 9} a b
puts "out-param: $a $b"
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
returned : 2 9
out-param: 2 9

Same answers, different contracts:

Returning a valueupvar out-parameter
Caller passesvaluesnames
Usable inside an expressionyesno
Can be called on a computed valueyesno — needs a variable first
Caller's variables touchednonewhichever names it passed
Reads asa functiona command with effects

The out-parameter version cannot be written as puts [minmax_out {5 2 9} a b], because its result is not the answer.

7. A worked example

A small set of list helpers that modify in place, in the style of lappend:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
proc list_push {listVar value} {
    upvar 1 $listVar l
    lappend l $value
}

proc list_pop {listVar} {
    upvar 1 $listVar l
    if {[llength $l] == 0} { error "cannot pop from an empty list" }
    set top [lindex $l end]
    set l [lrange $l 0 end-1]
    return $top
}

proc list_clear {listVar} {
    upvar 1 $listVar l
    set l {}
}

set stack {}
list_push stack a
list_push stack b
list_push stack c
puts "stack  : $stack"
puts "popped : [list_pop stack]"
puts "stack  : $stack"
list_clear stack
puts "cleared: '[set stack]' ([llength $stack] items)"

if {[catch {list_pop stack} m]} { puts "error  : $m" }
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
stack  : a b c
popped : c
stack  : a b
cleared: '' (0 items)
error  : cannot pop from an empty list

Each procedure takes a name, aliases it once at the top, and then works with an ordinary local. list_pop both modifies in place and returns a value, which is a reasonable combination when the returned thing is the item removed rather than the collection.

8. Exercises

Exercise 1. Why does push results "ok" pass results rather than $results?

Exercise 2. What do levels 1, #0 and 0 mean, and which is the default?

Exercise 3. What error does this give, and why?

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
proc f {v} {
    upvar 1 v v
    return $v
}
f hello

Exercise 4. Write bump_by taking the name of a counter and an amount, adding the amount to the caller's variable.

Exercise 5. Why is upvar the only way to pass an array to a procedure?

Exercise 6. Give two reasons to prefer returning a value over an upvar out-parameter, and the cases where upvar wins anyway.

Answers

1. Because upvar links to a variable by name. $results would substitute the list's contents, and the procedure would receive a value with no way to find the variable it came from.

2. 1 is the caller's frame, #0 is the global frame, and 0 is the procedure's own frame. 1 is the default when the level is omitted.

3. variable "v" already exists. The parameter v is a local created before the body runs, and upvar will not rebind a name that is already in use. Rename either the parameter or the alias.

4.

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
proc bump_by {countVar amount} {
    upvar 1 $countVar c
    if {![info exists c]} { set c 0 }
    incr c $amount
}

set hits 5
bump_by hits 3
bump_by hits 2
puts $hits
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
10

5. Because an array is not a value — there is nothing to pass or return, as Chapter 7.1 showed. Only its name can be handed over, and upvar is what turns that name back into usable access.

6. Returning a value can be used inside an expression and called on a computed argument, and it touches none of the caller's variables. upvar wins when the data is an array, when something must genuinely be modified in place, or when the surrounding code already works that way.

9. Remember

  • upvar ?level? otherName localName makes a local name refer to a variable in another frame.
  • The caller passes a bare name, not $name — the procedure needs the name, not the value.
  • Level 1 is the caller and is the default; #0 is global; 0 is the procedure's own frame. Write the 1 anyway.
  • It creates the caller's variable if it does not exist, and info exists through the link is a safe test beforehand.
  • It refuses a local name already in use — variable "x" already exists — including when that name is a parameter.
  • Suffix the name-carrying parameter (listVar, arrName) so the alias cannot collide.
  • Arrays can only be passed this way, which is the promise from Module 7 delivered.
  • It is a weaker contract than returning a value — not usable in an expression, not callable on a computed argument. Use it for arrays, genuine in-place modification, or an existing convention.

Where this fits

Part of the Tcl curriculum.