Search code examples
returntclproc

How works return -code $value from the proc in TCL


I must use a proc Z, which returns something like "return -code 2" $something , but I can't understand how it's works.

proc X {} {
    puts "X - in"
    Y
    puts "X - out"
}
proc Y {} {
    puts "Y - in"
    Z
    puts "Y - out"
}
proc Z {} {
    puts "Z - in"
    return -code 2 "some value"
    puts "Z - out"
}
X

Output is :

X - in
Y - in
Z - in
X - out

I can't change return in proc Z, but I also want that it continues his work in proc Y and puts "Y-out". Can i do it ?


Solution

  • The return command conceptually raises an exception (of type return, which corresponds to the TCL_RETURN result code in the Tcl C API) that is trapped by the procedure (strictly, by the procedure hull that does things like callframe management and argument marshalling) in which it runs to make that do something. In the case of a normal return, it simply makes the procedure return that value, but when the -code option is provided, it can direct the procedure to do something else; this is done after the callframe has been popped from the Tcl call stack.

    Because 2 is the actual value of the TCL_RETURN result code, it means that it is telling the procedure hull (i.e., the overall Z procedure) to behave like it was a simple call to return, which is why puts "Y - out" isn't being executed; there's a return exception being processed as a result of the execution of Z. (FWIW, return -code 2 is pretty rare. return -code 1/return -code error is much more common, as it is a way of getting an error exception to be thrown, which is useful because stack traces are only built by error exceptions.)

    You can override this processing using catch or try. It's a bit easier to get this right with try, since catch is a fairly broad-brush primitive. Try this:

    proc Y {} {
        puts "Y - in"
        try {
            Z
        } on return {msg} {
            puts "Z told us to return '$msg'"
        }
        puts "Y - out"
    }
    

    If you're just wanting to make a short piece of code work after the call but otherwise not interfere, tryfinally is the right way:

    proc Y {} {
        puts "Y - in"
        try {
            Z
        } finally {
            puts "Y - out"
        }
    }
    

    If you're using Tcl 8.5 (or before), you won't have try. Here's a version with catch:

    proc Y {} {
        puts "Y - in"
        set code [catch {
            Z
        } msg]
        if {$code == 2} {
            puts "Z told us to return '$msg'"
        }
        puts "Y - out"
    }
    

    The problem with catch is that it is very easy to lose errors by accident.