Search code examples
tclupvar

how the upvar command works in TCL?


I have a question about upvar command in TCL. Using upvar command, we have have a reference to a global variable or a local variable in other procedure. I saw the following code:

proc tamp {name1 name2} {
    upvar $name1 Ronalod
    upvar $name2 Dom 
    set $Dom "Dom"
}

this procedure is called as tamp name1 name2 , and there is no global variables name1, name2 defined outside of it, how this upvar works in this case?


Solution

  • When you call upvar 1 $foo bar, it finds the variable in the caller's scope whose name is in the foo variable, and makes the local bar variable into an alias for it. If the variable doesn't exist, it is created in an unset state (i.e., the variable record exists but it has no value. In fact, the implementation uses a NULL to represent that information, which is why Tcl has no NULL equivalent; NULL indicates non-existence) but the link is still created. (It only gets torn down when the local scope is destroyed or if upvar is used to point the local variable at something else.)

    So let's look at what your code is really doing:

    proc tamp {name1 name2} {
        upvar $name1 Ronalod
        upvar $name2 Dom 
        set $Dom "Dom"
    }
    

    The first line says that we're creating a command called tamp as a procedure, that that procedure will have two mandatory formal arguments, and that those arguments are called name1 and name2.

    The second line says that we're binding a variable name in the caller (the 1 level indicator from my earlier explanation is optional, but strongly advised in idiomatic code) that is given by the name1 variable (i.e., the first argument to the procedure) to the local variable Ronalod. Henceforth, all accesses to that local variable (until the end of the stack frame's life) will be actually performed on the bound variable in the caller.

    The third line is pretty much the same, except with name2 (second argument) and Dom (local variable).

    The fourth line is actually pretty funky. It reads the Dom variable to get a variable name (i.e., the variable named in the second argument to the procedure call) and sets that named variable to the value Dom. Remember, in Tcl you use $ to read from a variable, not to talk about the variable.

    The result of the procedure call will be the result of the last command in its body (i.e., the literal Dom since set yields the contents of the variable as its result, a value it has just assigned). (The last line is wholly uninteresting as it is just the end of the procedure body.)

    The net result of calling this command is actually going to be pretty much nothing at all unless the second argument names a variable containing either Ronalod or Dom. That's pretty confusing. Of course, the confusing bit is really that funky set with a variable first argument. (That's almost always a bad idea; it's an indicator of Bad Code Smell.) Had you used this instead, things would have been simpler:

    set Dom "Dom"
    

    In this case, the variable that Dom is coupled to (i.e., the one named by the second argument to the procedure) would be set to Dom; the variables would in effect be being passed-by-reference. That extra $ makes a huge difference!