unset , a built-in Tcl command, removes one or more variables.


unset ?-nocomplain? varName ?varName ...?


official reference


It is an error for the variables to not already exist, the name to be illegal, etc. This error can be suppressed with the -nocomplain option.

A trace on the variable can resurrect it.

DKF: In 8.6, unset is bytecode compiled. (And don't use catch {unset foo}; the operationally-equivalent unset -nocomplain foo generates much better bytecode.)

In retrospect, unset should have been designed so that it was not an error to unset a variable that didn't exist, in which case -nocomplain would not have been necessary.

aspect: more support for the above? I removed it earlier as it's not a sentiment I hear often in the community, and error-by-default seems the appropriate behaviour to me. Usually (ime) a "no such variable!" error from unset identifies a typo I'd rather catch early rather than a spot for -nocomplain.

PYK 2014-08-15: I added the comment in question after unset was mentioned in the Tcl chatroom on 2014-08-12. My understanding is that -nocomplain for unset and glob are considered ugly workarounds necessitated by an unfortunate design decision, and that in general, Tcl commands opt to do less checking for the sake of more concise scripts, allowing the programmer to add their own checks where desired. If error-by-default was the more appropriate behaviour, the following would raise an error:

set x 1
set x 2

Leading to:

set -nocomplain x 1
set -nocomplain x 2

Unset and Traces

Unsetting a variable will remove all the traces on it. If there are any variable unset traces, they will first be called.

Proc-local variables are implicitly unset when the proc returns. By using an unset trace, we can do sneaky things:

package require lambda 
proc finally {script} {
    set v [lindex [uplevel 1 {info locals}] 0]
    tailcall trace add variable $v unset [lambda args $script]

proc test {n cmd} {
    finally {puts "Returning"}
    for {set x 0} {$x < $n} {incr x} {
        puts "$x ... [{*}$cmd]"

test 3 {info level}
# 0 ... 1
# 1 ... 1
# 2 ... 1
# Returning
test 3 {expr 1/0}
# Returning
# ERROR: divide by zero
# while evaluating {test 3 {expr 1/0}}

Unset and Tk -variable options

Unsetting a variable that is still bound to an entry widget through the -textvariable option (for example), strange things may happen (they did in 2002 ...). Beware if you do this!

Tcl8.6 offers a number of ways to protect against this, which amount to tying the object's lifecycle to the variable. You can place them both in an object, or use an unset trace on the variable, or interpose a megawidget that binds correctly with namespace upvar ...

Unset and Upvar

Unsetting an upvar-bound variable will also unset all its other bindings (thanks PYK for correcting previous text here):

set var1 one

proc p1 {} {
    upvar 1 var1 var1 
    unset var1

puts [info exists var1] ;# -> 1
puts [info exists var1] ;# -> 0

If you want to redirect an upvar binding, you can simply "upvar over the top of it":

oo::class create Binder {
    constructor {} {}
    variable var
    method bind {name} {
        upvar 1 $name [self namespace]::var
    method exists {} {
        info exists var
    method get {} {
        set var
    method set {value} {
        set var $value

Binder create bee
bee bind foo
puts [bee exists]   ;# -> 0
incr foo 100
puts [bee exists]   ;# -> 1
puts [bee get]      ;# -> 100
bee bind bar
puts [bee exists]   ;# -> 0
set bar check
puts [bee get]      ;# -> check

Note that "upvar over the top" will not work for plain variables (those not created by upvar):

Binder create wasp
wasp set bzzzz      ;# this creates the namespace variable without [upvar]
wasp bind foo       ;# ERROR: variable "::oo::Obj16::var" already exists

This has implications for creating objects that mimic Tk's -variable (resp. -textvariable, -listvariable) option.