Tcl_TraceVar(3)
๐ NAME ๐
Tcl_TraceVar, Tcl_TraceVar2, Tcl_UntraceVar, Tcl_UntraceVar2, Tcl_VarTraceInfo, Tcl_VarTraceInfo2 โ bestie, these are the monitor accesses to a variable girlies. That's it, that's the job. โจ
So like, quick vibe check first: this is from the Tcl Library Procedures set, the Tcl_TraceVar(3) page specifically. And it's a long one, so buckle up โ we're doing this in two parts, and this is part one. ๐
โจ SYNOPSIS โจ
Okay so first you gotta invite the header in:
#include <tcl.h>
Then, the whole squad:
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)
๐ซถ ARGUMENTS ๐ซถ
Here's the cast, no filler, everybody's a main character:
Tcl_Interp *interp(in) โ the interpreter that's holding the variable. ๐const char *varName(in) โ the name of the variable. And listen, it's flexible: it may refer to a scalar variable, to an array variable with no index, or to an array variable with a parenthesized index. She contains multitudes.int flags(in) โ an OR-ed combo 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. Not every flag gets used by every procedure, okay, they all have their own thing going on. More on this below. โฌ๏ธTcl_VarTraceProc *proc(in) โ the procedure that gets invoked whenever one of the traced operations goes down. ๐void *clientData(in) โ an arbitrary one-word value you hand over toproc. It's giving "little gift for later."const char *name1(in) โ the name of a scalar or array variable, without the array index.const char *name2(in) โ when you're tracing an element of an array, this is the index of that element. For traces on scalar variables or on whole arrays? It'sNULL. Periodt.void *prevClientData(in) โ if it's non-NULL, it gives the last value returned byTcl_VarTraceInfoorTcl_VarTraceInfo2, so this call will return info about the next trace. If it'sNULL, this call returns info about the first trace. It's a whole little relay race. ๐โโ๏ธ
๐ธ DESCRIPTION ๐ธ
Okay so the moment you've been waiting for. Tcl_TraceVar lets a C procedure monitor and control access to a Tcl variable, so that your C procedure gets invoked whenever that variable is read, written, or unset. She's watching. Respectfully. ๐
If the trace is created successfully then Tcl_TraceVar returns TCL_OK. But if an error occurred โ like, say, varName specifies an element of an array, but the actual variable is not an array โ then TCL_ERROR is returned and an error message is left in the interpreter's result. Rude, but informative. ๐ญ
The flags argument to Tcl_TraceVar tells you when the trace procedure is supposed to be invoked and gives info for setting up the trace. It's an OR-ed combination of any of the following values, and yes, we're going through all of them:
TCL_GLOBAL_ONLY
Normally the variable gets looked up at the current level of procedure call; but if this bit is set, then the variable is looked up at global level, ignoring any active procedures. She said "no locals, we're going global." ๐
TCL_NAMESPACE_ONLY
Normally the variable gets looked up at the current level of procedure call; but if this bit is set, then the variable is looked up in the current namespace, ignoring any active procedures.
TCL_TRACE_READS
Invoke proc whenever an attempt is made to read the variable. ๐
TCL_TRACE_WRITES
Invoke proc whenever an attempt is made to modify the variable. โ๏ธ
TCL_TRACE_UNSETS
Invoke proc whenever the variable is unset. And a variable can be unset in a bunch of ways, bestie: either explicitly by an unset command, or implicitly when a procedure returns (its local variables are automatically unset), or when the interpreter or namespace is deleted (all variables are automatically unset). Three different breakups, same energy. ๐
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. Sneaky little detail, iykyk. ๐
TCL_TRACE_RESULT_DYNAMIC
The result of invoking the proc is a dynamically allocated string that will be released by the Tcl library via a call to Tcl_Free. Must not be specified at the same time as TCL_TRACE_RESULT_OBJECT. Pick a lane, girl. ๐
TCL_TRACE_RESULT_OBJECT
The result of invoking the proc is a Tcl_Obj* (cast to a char*) with a reference count of at least one. The ownership of that reference will be transferred to the Tcl core for release (when the core has finished with it) via a call to Tcl_DecrRefCount. Must not be specified at the same time as TCL_TRACE_RESULT_DYNAMIC. Same warning, same energy. ๐
Whenever one of the specified operations occurs on the variable, proc will be invoked. It should have arguments and result that match 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 those passed to Tcl_TraceVar when the trace was created. clientData typically points to an application-specific data structure that describes what to do when proc is invoked. Name1 and name2 give the name of the variable that triggered the callback in the normal two-part form (see the description of Tcl_TraceVar2 below for details). And here's a lil plot twist: in case name1 is an alias to an array element (created through facilities such as upvar), name2 holds the index of the array element, rather than NULL. ๐
Flags is an OR-ed combination of bits providing several pieces of information. One of the bits TCL_TRACE_READS, TCL_TRACE_WRITES, TCL_TRACE_ARRAY, or TCL_TRACE_UNSETS will be set in flags to indicate which operation is being performed on the variable. The bit TCL_GLOBAL_ONLY will be 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 attempts to access the variable. The bit TCL_NAMESPACE_ONLY will be set whenever the variable being accessed is a namespace 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 attempts to access the variable. The bit TCL_TRACE_DESTROYED will be set in flags if the trace is about to be destroyed; this information may be useful to proc so that 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 information on other possibilities. ๐ซก
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 the corresponding trace is removed. If no such trace exists, then the call to Tcl_UntraceVar has no effect. (Very "no thoughts, head empty" of it. ๐ฆ) 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 is used; other bits are ignored) and its trace procedure must 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 the prevClientData argument is not NULL, then it should be the return value from a previous call to Tcl_VarTraceInfo. In this case, the new return value will correspond 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 of the traces for a given variable that have the same proc. Iconic iterator behavior, honestly. ๐ฅ
๐ฆ 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 array, and 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, it means that either the variable is a scalar or the trace is to be set on the entire array rather than an individual element (see WHOLE-ARRAY TRACES below for more information).
โฆand bestie, that's literally only part one. part two is coming, don't go anywhere ๐ โจ
๐ซง ACCESSING VARIABLES DURING TRACES ๐ซง
okay bestie so here's the tea: while you're inside a read, write, or array trace, proc is allowed to read, write, or unset the traced variable โ using Tcl_GetVar2, Tcl_SetVar2, and all their little friends. ๐
the trick is that while proc is running, traces are temporarily disabled for that variable. so when you call Tcl_GetVar2 and Tcl_SetVar2 in there, you will NOT trigger proc again, or any other trace procedures either. no infinite loop energy, we love to see it. โจ
and it's only the variable whose trace procedure is active that gets the mute button โ touching other variables still gets traced like normal. ๐
โ ๏ธ but heads up: if a variable gets unset during a read or write trace, the unset traces WILL fire. no skipping those.
now, during unset traces, it's a whole different vibe โ the variable has already been completely expunged. ๐ your trace procedure can still read or write it, but congrats, that's a brand new version of the variable, fresh out the box. ๐
and here's the thing: traces are not disabled during unset traces the way they are for read and write traces. but the existing traces have already been removed from the variable before any trace procedures get invoked. so if an unset trace procedure sets new traces, those new traces will get invoked on accesses to the variable made by the trace procedures. wild, i know. ๐ฆ
โฐ CALLBACK TIMING โฐ
so when is proc actually getting called? depends on the flavor, bestie:
- read tracing โ
procfires whenever the variable's value is read. that meanssetTcl commands,$-notation in Tcl commands, and calls toTcl_GetVarandTcl_GetVar2.procis invoked just before the value is returned, and it can modify the value to affect what the traced access returns. and if it unsets the variable? the access returns an error, exactly like the variable never existed at all. ๐ญ - write tracing โ
procfires whenever the variable's value gets modified. that includessetcommands, commands that modify variables as side effects (hicatchandscan๐), and calls toTcl_SetVarandTcl_SetVar2.procis 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 decide what actually gets returned by the traced access. and if it deletes the variable? the traced access returns an empty string. cute. ๐ - array tracing โ
procfires at the beginning of the array command implementation, before any of the operations likeget,set, ornameshave been invoked. the trace procedure can modify array elements withTcl_SetVarandTcl_SetVar2. ๐ - unset tracing โ
procfires whenever the variable is destroyed. and the traces are called after the variable has been completely unset. periodt.
๐ WHOLE-ARRAY TRACES ๐
if you call Tcl_TraceVar or Tcl_TraceVar2 with the name of an array variable but no index into that array, then the trace gets set on the array as a whole. ๐
translation: proc gets invoked whenever any element of the array is accessed in the ways specified by flags. it's giving group project, but like, a good one.
when an array is unset, a whole-array trace gets invoked just once โ with name1 equal to the name of the array and name2 NULL. it does NOT get invoked once for each element. thank god, honestly. ๐
๐ฏ MULTIPLE TRACES ๐ฏ
yes bestie, you can absolutely have multiple traces on the same variable. main character energy for everyone. โจ
when that happens, all of the trace procedures get invoked on each access โ in order from most-recently-created to least-recently-created. newest first, always.
and when you've got whole-array traces on 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 of the unset traces get invoked but the remainder of the read and write traces get skipped. ๐
๐จ ERROR RETURNS ๐จ
under normal conditions, trace procedures should return NULL, which means "we're good, successful completion, slay." โ
if proc returns a non-NULL value, that 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, which 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 careful: 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 free lunches.
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. so don't bother returning anything spicy there. ๐
๐ง RESTRICTIONS ๐ง
because operations on variables can happen 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 for. Tcl_InterpDeleted is the important tool here. ๐ ๏ธ
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. no cap, it's over. ๐
and here's another one: a trace procedure can be called at any time, even when there are partially formed results stored in the interpreter. ๐ณ so if the trace procedure does anything that could damage this result (like 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 vibes. ๐ซถ
๐ป UNDEFINED VARIABLES ๐ป
it is legal to set a trace on an undefined variable. yes really. ๐ธ
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. iconic, honestly. ๐
๐ท๏ธ 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. ๐
๐งฎ 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 may have additional references made to it (e.g., by setting it as the interpreter result with Tcl_SetObjResult). balance those counts, bestie, or the memory leaks will find you. ๐ซถ
๐ BUGS ๐
- array traces are not yet integrated with the Tcl
info existscommand - nor is there Tcl-level access to array traces
(we're all waiting. patiently. sort of. ๐ญ)
๐ SEE ALSO ๐
๐ KEYWORDS ๐
clientData, trace, variable
okay that's the whole page bestie, you're officially a trace expert now ๐ this was Tcl 9.0, the Tcl_TraceVar(3) page โ go forth and trace responsibly, mwah ๐โจ๐
๐ฆ where this page comes from
- alpine 3.23: tcl9-doc 9.0.3-r0 ยท TCL
- alpine 3.24: tcl9-doc 9.0.3-r2 ยท TCL
- fedora 43: tcl-doc 1:9.0.2-1.fc43 ยท TCL AND GPL-3.0-or-later WITH Bison-exception-2.2 AND BSD-3-Clause
- fedora 44: tcl-doc 1:9.0.2-1.fc44 ยท TCL AND GPL-3.0-or-later WITH Bison-exception-2.2 AND BSD-3-Clause
- nixos 26.05: tcl 9.0.3
- 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