[[explain importance of this notion of immutability to proper understanding of Tcl's semantics]] Those who have a little knowledge of C (or other similar languages) have learned to pass arguments to a function ''by value'' or ''by reference''. Tcl scripts have such functionalities : when you need to pass the variable ''by value'', you put a '''$''' before the variable name. And this ''does not modifiy'' the original variable. But when you need to access uplevel variables, you can use [upvar] in the proc you call, passing just the name of the variable as argument. Then you can access the content of a variable defined in the upper stack level. This feature works especially well with [array]s that cannot be passed by value. But when you come to Tcl C API (the internals that are required to write C extensions), you must know how Tcl passes variables by value, which makes use of the ''copy-on-write'' semantics. ---- Basically the ''copy-on-write'' semantics are: * when a ''proc'' receives a [Tcl_Obj], it is in fact a pointer to the original variable, which is stored in a [Tcl_Obj] structure. * when all you need is ''read the variable'', you do not require copying the data ('''1'''). The variable is ''shared''. * when you need to work with a modified version of this variable, you need ('''2''') to check whether the variable ''is shared or not'' * if the variable is ''shared'', then you need to ''duplicate its content''. ('''3''') ---- '''A surprise''' Here are 2 procs, the first gets a list passed ''by value'', the second ''by reference'' via [upvar]. # Proc 1 proc lmul-byval {list mult} { foreach i $list { lappend out [expr {$i*$mult}] } return $out } # Proc 2 proc lmul-byref {list mult} { upvar 1 $list l for {set i 0} {$i<[llength $l]} {incr i} { lset l $i [expr {[lindex $l $i] * $mult}] } return } # Timings proc timeit {lsize val mult} { for {set i 0} {$i<$lsize} {incr i} {lappend list1 $val;lappend list2 $val} puts "By value : [time {set list1 [lmul-byval $list1 $mult]} 10]" puts "By reference : [time {lmul-byref list2 $mult} 10]" } foreach size {100 1000 10000} { puts "TIME : $size elements" timeit $size 2 2 } ''Results:'' TIME : 100 elements By value : 41.1 microseconds per iteration By reference : 84.0 microseconds per iteration TIME : 1000 elements By value : 386.9 microseconds per iteration By reference : 764.8 microseconds per iteration TIME : 10000 elements By value : 4300.3 microseconds per iteration By reference : 7893.7 microseconds per iteration As you can see, upvar is costly. Another example : proc checka {l} { if {[llength $l] == 1} { if {![string is integer $l]} {error "non-numerical value (should be integer)"} return [list bignum $l] } return $l } proc checkb {v} { upvar 1 $v l if {[llength $l] == 1} { if {![string is integer $l]} {error "non-numerical value (should be integer)"} set l [list bignum $l] } } In the above example, we check a list represents a bignum, and if it is a simple integer, makes a bignum of it.(assuming bignums are lists of integers with a "bignum" heading tag) foreach i {1 {bignum 2} {bignum 3 3 3 3 3 3 3 3 3}} { set a $i puts "By value: [time {set a [checka $a]} 100]" set b $i puts "By reference: [time {checkb b} 100]" } By value: 2.61 microseconds per iteration By reference: 3.42 microseconds per iteration By value: 2.11 microseconds per iteration By reference: 2.62 microseconds per iteration By value: 2.1 microseconds per iteration By reference: 2.51 microseconds per iteration This demonstrates that [upvar] is more time-wasting than passing arguments by value and returning it, for '''this perticular''' kind of tasks. ---- '''Example in C''' (taken from [foriter - a loop command]) '''1''' (just read the variable) result = Tcl_GetIntFromObj(interp, objv[2], &start); if (result != TCL_OK) { return result; } ... '''2''' (check if the variable is shared) if ( Tcl_IsShared( obj_counter ) ) { '''3''' (duplicate shared data) obj_counter = Tcl_DuplicateObj( obj_counter ); Increment reference counter (see [Tcl_Obj refCount HOWTO]) Tcl_IncrRefCount( obj_counter ); Remember that the [Tcl_Obj] is shared isSharedObjCounter=1; } ... When the [Tcl_Obj] becomes useless, we can decrement refcount to allow freeing memory. Without that, the whole interpreter may crash without alerting you :-( ... /* ending of the loop */ if ( isSharedObjCounter ) { Tcl_DecrRefCount( obj_counter ); } [[and consequences for extension design]] ???