Tcl_TraceVar(3tcl)

๐Ÿ“ฆ
version 9.0.4 ยท 3 versions
, in debian trixie, ubuntu questing, ubuntu resolute, ubuntu stonking

๐Ÿ’– 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 values TCL_TRACE_READS, TCL_TRACE_WRITES, TCL_TRACE_UNSETS, TCL_TRACE_ARRAY, TCL_GLOBAL_ONLY, TCL_NAMESPACE_ONLY, TCL_TRACE_RESULT_DYNAMIC and TCL_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 to proc. 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's NULL.
  • void *prevClientData (in) โ€” if it's non-NULL, it's the last value returned by Tcl_VarTraceInfo or Tcl_VarTraceInfo2, so this call returns info about the next trace. if it's NULL, 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 โ€” invoke proc whenever someone attempts to read the variable. ๐Ÿ‘€
  • TCL_TRACE_WRITES โ€” invoke proc whenever someone attempts to modify the variable. โœ๏ธ
  • TCL_TRACE_UNSETS โ€” invoke proc whenever the variable is unset. and bestie, a variable can get unset in so many ways: explicitly by an unset command, 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 โ€” invoke proc whenever the array command is invoked. this gives the trace procedure a chance to update the array before array names or array get is called. note that this is called before an array set, but that will trigger write traces. so it's like, two events, different vibes. ๐Ÿ”ฅ
  • TCL_TRACE_RESULT_DYNAMIC โ€” the result of invoking proc is a dynamically allocated string that the Tcl library will release via a call to Tcl_Free. โš ๏ธ must not be specified at the same time as TCL_TRACE_RESULT_OBJECT.
  • TCL_TRACE_RESULT_OBJECT โ€” the result of invoking proc is a Tcl_Obj* (cast to a char*) 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 to Tcl_DecrRefCount. โš ๏ธ must not be specified at the same time as TCL_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_ONLY is 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 like Tcl_GetVar if it tries to access the variable.
  • the bit TCL_NAMESPACE_ONLY is 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 like Tcl_GetVar if it tries to access the variable.
  • the bit TCL_TRACE_DESTROYED is set in flags if the trace is about to be destroyed. this info may be useful to proc so it can clean up its own internal data structures (see the section TCL_TRACE_DESTROYED below 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.

  • Name1 gives the name of a scalar variable or an array.
  • name2 gives 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:

  • set Tcl commands
  • $-notation in Tcl commands
  • invocations of the Tcl_GetVar and Tcl_GetVar2 procedures

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:

  • set commands
  • commands that modify variables as side effects (like catch and scan)
  • calls to the Tcl_SetVar and Tcl_SetVar2 procedures

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 to char* and to be released with Tcl_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