Tcl_TraceVar(3tcl)
๐ Tcl_TraceVar โ from the Tcl Library Procedures manual (section 3tcl) ๐
okay bestie, gather round, because today we're learning how to stalk a variable. not in a creepy way!! in a "the C code wants to know every time this variable gets read, written, or unset" way. it's giving surveillance but make it legal. ๐
โจ NAME โจ
Tcl_TraceVar, Tcl_TraceVar2, Tcl_UntraceVar, Tcl_UntraceVar2, Tcl_VarTraceInfo, Tcl_VarTraceInfo2 โ monitor accesses to a variable
like. the whole squad. six functions, one mission: watch a Tcl variable and vibe-check everything that touches it. ๐ฆ
โจ SYNOPSIS โจ
#include <tcl.h>
int
Tcl_TraceVar(interp, varName, flags, proc, clientData)
int
Tcl_TraceVar2(interp, name1, name2, flags, proc, clientData)
Tcl_UntraceVar(interp, varName, flags, proc, clientData)
Tcl_UntraceVar2(interp, name1, name2, flags, proc, clientData)
void *
Tcl_VarTraceInfo(interp, varName, flags, proc, prevClientData)
void *
Tcl_VarTraceInfo2(interp, name1, name2, flags, proc, prevClientData)
no cap, that's the whole menu. #include <tcl.h> first tho, always. ๐
โจ ARGUMENTS โจ
Tcl_Interp *interp(in) โ the interpreter that's holding the variable. your girl's whole world basically.const char *varName(in) โ name of the variable. it can point at a scalar variable, at an array variable with no index, or at an array variable with a parenthesized index. all three are valid, she contains multitudes. โจint flags(in) โ an OR-ed combination of the valuesTCL_TRACE_READS,TCL_TRACE_WRITES,TCL_TRACE_UNSETS,TCL_TRACE_ARRAY,TCL_GLOBAL_ONLY,TCL_NAMESPACE_ONLY,TCL_TRACE_RESULT_DYNAMICandTCL_TRACE_RESULT_OBJECT. heads up: not all flags are used by all procedures. scroll down for the full tea.Tcl_VarTraceProc *proc(in) โ the procedure to invoke whenever one of the traced operations happens. your little alarm system. ๐จvoid *clientData(in) โ arbitrary one-word value passed toproc. anything you want, she's not judging.const char *name1(in) โ name of a scalar or array variable (without the array index).const char *name2(in) โ for a trace on an element of an array, this gives the index of that element. for traces on scalar variables or on whole arrays, it'sNULL.void *prevClientData(in) โ if it's non-NULL, it's the last value returned byTcl_VarTraceInfoorTcl_VarTraceInfo2, so this call returns info about the next trace. if it'sNULL, this call returns info about the first trace. it's a lil pointer-based conga line. ๐
โจ DESCRIPTION โจ
Tcl_TraceVar lets a C procedure monitor and control access to a Tcl variable, so the C procedure gets invoked whenever that variable is read, written, or unset. imagine a tiny bouncer at the door of your variable, checking every single guest. iconic behaviour honestly.
if the trace gets created successfully, Tcl_TraceVar returns TCL_OK. if something went wrong โ like, say, varName specifies an element of an array but the actual variable is not an array โ then TCL_ERROR comes back and an error message is left in the interpreter's result. so rude of it, but okay. ๐ญ
the flags argument to Tcl_TraceVar tells you when the trace procedure fires, and also carries setup info. it's an OR-ed combination of any of these:
TCL_GLOBAL_ONLYโ normally the variable is looked up at the current level of procedure call. set this bit and the variable gets looked up at global level instead, ignoring any active procedures. main character energy, skipping the line. ๐TCL_NAMESPACE_ONLYโ normally the variable is looked up at the current level of procedure call. set this bit and it gets looked up in the current namespace, ignoring any active procedures.TCL_TRACE_READSโ invokeprocwhenever someone attempts to read the variable. ๐TCL_TRACE_WRITESโ invokeprocwhenever someone attempts to modify the variable. โ๏ธTCL_TRACE_UNSETSโ invokeprocwhenever the variable is unset. and bestie, a variable can get unset in so many ways: explicitly by anunsetcommand, implicitly when a procedure returns (its local variables are automatically unset), or when the interpreter or namespace is deleted (at which point all variables are automatically unset). it's giving every exit is a plot twist. ๐TCL_TRACE_ARRAYโ invokeprocwhenever thearraycommand is invoked. this gives the trace procedure a chance to update the array beforearray namesorarray getis called. note that this is called before anarray set, but that will trigger write traces. so it's like, two events, different vibes. ๐ฅTCL_TRACE_RESULT_DYNAMICโ the result of invokingprocis a dynamically allocated string that the Tcl library will release via a call toTcl_Free. โ ๏ธ must not be specified at the same time asTCL_TRACE_RESULT_OBJECT.TCL_TRACE_RESULT_OBJECTโ the result of invokingprocis aTcl_Obj*(cast to achar*) with a reference count of at least one. ownership of that reference gets transferred to the Tcl core for release (once the core is done with it) via a call toTcl_DecrRefCount. โ ๏ธ must not be specified at the same time asTCL_TRACE_RESULT_DYNAMIC.
whenever one of the specified operations happens on the variable, proc gets invoked. it should have arguments and a result matching the type Tcl_VarTraceProc:
typedef char *Tcl_VarTraceProc(
void *clientData,
Tcl_Interp *interp,
const char *name1,
const char *name2,
int flags);
the clientData and interp parameters will have the same values as the ones passed to Tcl_TraceVar when the trace was created. clientData typically points to an application-specific data structure describing what to do when proc fires. Name1 and name2 give the name of the variable that triggered the callback, in the usual two-part form (peep the Tcl_TraceVar2 description below for the deets). and listen: in the case where name1 is an alias to an array element โ created through facilities like upvar โ then name2 holds the index of the array element rather than NULL. sneaky but valid. ๐
Flags is an OR-ed combination of bits carrying several pieces of info. one of the bits TCL_TRACE_READS, TCL_TRACE_WRITES, TCL_TRACE_ARRAY, or TCL_TRACE_UNSETS will be set in flags to show which operation is being performed on the variable.
- the bit
TCL_GLOBAL_ONLYis set whenever the variable being accessed is a global one not accessible from the current level of procedure call โ the trace procedure will need to pass this flag back to variable-related procedures likeTcl_GetVarif it tries to access the variable. - the bit
TCL_NAMESPACE_ONLYis set whenever the variable being accessed is a namespace one not accessible from the current level of procedure call โ same deal, the trace procedure needs to pass this flag back to variable-related procedures likeTcl_GetVarif it tries to access the variable. - the bit
TCL_TRACE_DESTROYEDis set inflagsif the trace is about to be destroyed. this info may be useful toprocso it can clean up its own internal data structures (see the sectionTCL_TRACE_DESTROYEDbelow for more details). ๐งน
the trace procedure's return value should normally be NULL. see ERROR RETURNS below for info on other possibilities โ don't get creative unless you read that part first, fr fr.
Tcl_UntraceVar may be used to remove a trace. if the variable specified by interp, varName, and flags has a trace set with flags, proc, and clientData, then that corresponding trace gets removed. if no such trace exists, then the call to Tcl_UntraceVar just... has no effect. it's a no-op, she's unbothered. ๐ the same bits are valid for flags as for calls to Tcl_TraceVar.
Tcl_VarTraceInfo may be used to retrieve information about traces set on a given variable. the return value from Tcl_VarTraceInfo is the clientData associated with a particular trace. the trace must be on the variable specified by the interp, varName, and flags arguments (only the TCL_GLOBAL_ONLY and TCL_NAMESPACE_ONLY bits from flags are used; other bits are ignored) and its trace procedure must be the same as the proc argument.
if the prevClientData argument is NULL, then the return value corresponds to the first (most recently created) matching trace, or NULL if there are no matching traces. if prevClientData is not NULL, then it should be the return value from a previous call to Tcl_VarTraceInfo. in that case the new return value corresponds to the next matching trace after the one whose clientData matches prevClientData, or NULL if no trace matches prevClientData or if there are no more matching traces after it. this mechanism makes it possible to step through all the traces for a given variable that have the same proc. it's a linked list but make it a scavenger hunt. ๐ต๏ธโโ๏ธ
๐ TWO-PART NAMES ๐
the procedures Tcl_TraceVar2, Tcl_UntraceVar2, and Tcl_VarTraceInfo2 are identical to Tcl_TraceVar, Tcl_UntraceVar, and Tcl_VarTraceInfo, respectively โ except that the name of the variable consists of two parts.
Name1gives the name of a scalar variable or an array.name2gives the name of an element within an array.
when name2 is NULL, name1 may contain both an array and an element name: if the name contains an open parenthesis and ends with a close parenthesis, then the value between the parentheses is treated as an element name (which can have any string value) and the characters before the first open parenthesis are treated as the name of an array variable. โจ
if name2 is NULL and name1 does not refer to an array element, that means either the variable is a scalar, or the trace is being set on the entire array rather than an individual element (see WHOLE-ARRAY TRACES below for more info). ๐ฆ
๐ ACCESSING VARIABLES DURING TRACES ๐
okay so this is the part where things get a lil meta, bestie. during read, write, and array traces, your proc is absolutely allowed to read, write, or unset the very variable it's tracing โ using Tcl_GetVar2, Tcl_SetVar2, and the other procedures. go off ๐
but here's the โจvibe checkโจ: while proc is running, traces are temporarily disabled for that variable. so calls to Tcl_GetVar2 and Tcl_SetVar2 won't re-trigger proc or any other trace procedures. no infinite loop nonsense, we love that for us.
- the disabling is only for the variable whose trace procedure is currently active โ accesses to other variables still get traced like normal.
- but if a variable gets unset during a read or write trace, then unset traces will be invoked. periodt.
during unset traces the variable has already been completely expunged. gone. poof. ๐ proc can still read or write it, but that's a brand new version of the variable, not the old one.
also, traces are not disabled during unset traces the way they are for read and write traces โ but existing traces have already been removed from the variable before any trace procedures get invoked. and if an unset trace procedure sets new traces? those new traces will get invoked on accesses to the variable by the trace procedures. it's giving layers. ๐ง
โฐ CALLBACK TIMING โฐ
read tracing: when read tracing is set on a variable, proc gets invoked whenever the variable's value is read. this includes:
setTcl commands$-notation in Tcl commands- invocations of the
Tcl_GetVarandTcl_GetVar2procedures
proc is invoked just before the variable's value is returned. it can modify the value to affect what the traced access returns. and if it unsets the variable? then the access returns an error, just as if the variable never existed. dramatic but valid. ๐
write tracing: when write tracing is set, proc gets invoked whenever the variable's value is modified. that includes:
setcommands- commands that modify variables as side effects (like
catchandscan) - calls to the
Tcl_SetVarandTcl_SetVar2procedures
proc is invoked after the value has been modified, but before the new value has been returned. it can modify the value to override the change and determine what the traced access actually returns. if it deletes the variable, the traced access returns an empty string. slay. ๐
array tracing: when array tracing is set, proc gets invoked at the very beginning of the array command implementation โ before any of the operations like get, set, or names have been invoked. the trace procedure can modify array elements with Tcl_SetVar and Tcl_SetVar2.
unset tracing: when unset tracing is set, proc gets invoked whenever the variable is destroyed. the traces get called after the variable has been completely unset.
๐ WHOLE-ARRAY TRACES ๐
if you call Tcl_TraceVar or Tcl_TraceVar2 with the name of an array variable and no index into the array, then the trace gets set on the array as a whole. iconic.
this means proc gets invoked whenever any element of the array is accessed in the ways specified by flags.
and when an array gets unset? a whole-array trace is invoked just once, with name1 equal to the name of the array and name2 NULL. it does not get invoked once for each element. we do not have time for that. ๐
๐ฏ MULTIPLE TRACES ๐ฏ
yes bestie, you can have multiple traces on the same variable. when that happens, all of the trace procedures get invoked on each access, in order from most-recently-created to least-recently-created. newest gets the mic first. ๐ค
and when there are whole-array traces for an array plus traces on individual elements, the whole-array traces are invoked before the individual-element traces.
oh and โ if a read or write trace unsets the variable, then all the unset traces get invoked, but the remainder of the read and write traces get skipped. yeah. that's the rule.
๐จ ERROR RETURNS ๐จ
under normal conditions, trace procedures should return NULL โ that's the "we're so good, everything's fine" signal.
if proc returns a non-NULL value, it means an error happened. the return value must be a pointer to a static character string containing an error message โ unless (exactly one of) the TCL_TRACE_RESULT_DYNAMIC and TCL_TRACE_RESULT_OBJECT flags is set. those flags say the result is either:
- a dynamic string (to be released with
Tcl_Free), or - a
Tcl_Obj*(cast tochar*and to be released withTcl_DecrRefCount)
...containing the error message.
if a trace procedure returns an error, no further traces are invoked for that access, and the traced access aborts with the given message. ๐
trace procedures can use this to make variables read-only, for example โ but note that the value of the variable will already have been modified before the trace procedure is called, so the trace procedure will have to restore the correct value itself. no cap.
also important: the return value from proc is only used during read and write tracing. during unset traces, the return value is ignored and all relevant trace procedures will always be invoked. it's giving unbothered. โจ
๐ง RESTRICTIONS ๐ง
because operations on variables may take place as part of the deletion of the interp that contains them, proc has to be careful about checking what the interp parameter can actually be used to do. the routine Tcl_InterpDeleted is an important tool for this. ๐ ๏ธ
when Tcl_InterpDeleted returns 1, proc will not be able to invoke any scripts in interp.
you may encounter old code using a deprecated flag value TCL_INTERP_DESTROYED to signal this condition, but Tcl 9 no longer supports this. any supported code must be converted to stop using it. that flag is not coming back, bestie. ๐ชฆ
also โ a trace procedure can be called at any time, even when there are partially formed results stored in the interpreter. if the trace procedure does anything that could damage this result (such as calling Tcl_Eval), then it must use the Tcl_SaveInterpState and related routines to save and restore the original state of the interpreter before it returns. protect the vibe. ๐ซถ
๐ป UNDEFINED VARIABLES ๐ป
it is legal to set a trace on an undefined variable. the variable will still appear to be undefined until the first time its value is set.
and if an undefined variable is traced and then unset, the unset will fail with an error (โno such variableโ) โ but the trace procedure will still be invoked. the audacity. ๐ญ
๐ท๏ธ TCL_TRACE_DESTROYED FLAG ๐ท๏ธ
in an unset callback to proc, the TCL_TRACE_DESTROYED bit is set in flags if the trace is being removed as part of the deletion.
traces on a variable are always removed whenever the variable is deleted. the only time TCL_TRACE_DESTROYED is not set is for a whole-array trace invoked when only a single element of an array is unset. iykyk. ๐
๐งฎ REFERENCE COUNT MANAGEMENT ๐งฎ
when a proc callback is invoked, and that callback was installed with the TCL_TRACE_RESULT_OBJECT flag, the result of the callback is a Tcl_Obj reference when there is an error.
the result will have its reference count decremented once when no longer needed โ or it may have additional references made to it (e.g., by setting it as the interpreter result with Tcl_SetObjResult). keep those counts balanced, bestie, it's self-care. โจ
๐ BUGS ๐
array traces are not yet integrated with the Tcl info exists command, nor is there Tcl-level access to array traces. we're aware. it's giving "known issue". ๐ซ
๐ SEE ALSO ๐
๐ KEYWORDS ๐
clientData, trace, variable
and that's the whole page, bestie!! ๐ this was Tcl_TraceVar(3tcl) from the Tcl 9.0 manual, and you read the ENTIRE thing, which is honestly main character energy. go trace something responsibly. mwah ๐โจ๐ฆ
๐ฆ where this page comes from
- debian trixie: tcl9.0-doc 9.0.1+dfsg-2
- ubuntu questing: tcl9.0-doc 9.0.2+dfsg-1
- ubuntu resolute: tcl9.0-doc 9.0.3+dfsg-1
- ubuntu stonking: tcl9.0-doc 9.0.4+dfsg-1
- Copyright (c) 1989-1993 The Regents of the University of California
- Copyright (c) 1994-1996 Sun Microsystems, Inc
- ๐ girlified by deepseek-ai/DeepSeek-V4.1-Flash on : a language model's rewrite of the original, under its terms