Tcl Part I · Module 10 · Procedures and Reuse
rename: Replacing or Wrapping an Existing Command
Everything in this module has been building to something that looks startling the first time you see it: in Tcl you can take an existing command — yours or a built-in — move it aside under a new name, and put your own command in its place that calls the original. That is how logging wrappers, timing instrumentation and safety checks get added to code that was never designed for them, without editing a single call site. It is also, in exactly the same mechanism, why a procedure that was accidentally defined twice is so hard to track down. This chapter covers both, because they are the same fact seen from two directions.
Intermediate12 min readTclPart IProceduresrenameWrappers
Part I · Module 10 · Chapter 10.9 · rename: Replacing or Wrapping an Existing Command
1. Why this is possible at all
Three facts from earlier in the module, which together make everything here follow:
procregisters a command in one flat, global table (Chapter 10.1 §2).- Redefining a name silently replaces it (Chapter 10.1 §6).
argsplus{*}$argsforwards any arguments without knowing the signature (Chapter 10.4 §4).
Nothing in this chapter is a new capability. It is those three used deliberately.
2. Renaming
proc helper {} { return "helped" }
rename helper helper_original
puts "helper still defined? [llength [info procs helper]]"
puts [helper_original]helper still defined? 0
helpedThe command moved. Its body is unchanged; only the name it answers to is different.
Renaming onto a name that already exists is refused, so you cannot destroy something by accident:
proc one {} { return 1 }
proc two {} { return 2 }
rename one twocan't rename to "two": command already exists3. Deleting
Renaming to the empty string removes a command:
proc temporary {} { return "here" }
puts "before: [llength [info procs temporary]]"
rename temporary {}
puts "after : [llength [info procs temporary]]"before: 1
after : 04. The wrapper
Put those together and you have the pattern:
proc fetch {id} {
return "record-$id"
}
# 1. move the original aside
rename fetch fetch_real
# 2. define a replacement that calls it
proc fetch {args} {
puts " (calling fetch with: $args)"
set result [fetch_real {*}$args]
puts " (fetch returned: $result)"
return $result
}
puts [fetch 42] (calling fetch with: 42)
(fetch returned: record-42)
record-42Every existing call to fetch now goes through the wrapper, and not one call site changed. The wrapper used args and {*}$args, so it did not need to know that fetch takes one argument — and will keep working if fetch_real gains another.
5. It works on built-ins too
rename llength _real_llength
proc llength {l} {
return [expr {[_real_llength $l] * 100}]
}
puts [llength {a b c}]
# put it back
rename llength {}
rename _real_llength llength
puts [llength {a b c}]300
3The first call returned 300 because llength was, for those few lines, a procedure of mine. Restoring it is two commands: delete the replacement, rename the original back.
6. The same mechanism is why a stray redefinition is invisible
Now the other direction. Because any file can define any name and the last one wins silently, a duplicate definition leaves no trace:
# as if from file A
proc check {x} { return "A says $x" }
# as if from file B, loaded later
proc check {x} { return "B says $x" }
puts [check hello]
puts "definitions registered: [llength [info procs check]]"B says hello
definitions registered: 1There is one check and it is B's. Nothing records that A ever defined one. If A's version was the correct one, the symptom is that check behaves wrongly — and reading A's source shows code that looks right.
7. Finding one
Three things that actually help.
Print the body you are really running. info body shows the definition that is live, not the one you are looking at:
proc check {x} { return "B says $x" }
puts [string trim [info body check]]return "B says $x"If that does not match the file you have open, something redefined it.
Detect the collision at definition time. A tiny guard converts a silent overwrite into a message:
proc define_once {name params body} {
if {[llength [info procs $name]] > 0} {
error "procedure '$name' is already defined"
}
proc $name $params $body
}
define_once safe {x} { return $x }
puts [safe 1]
if {[catch {define_once safe {x} { return "different" }} m]} {
puts "rejected: $m"
}1
rejected: procedure 'safe' is already definedNote proc $name $params $body — proc is an ordinary command, so its arguments can come from variables. That is Chapter 10.1 §2 being useful rather than merely interesting.
Trap it with a wrapper on proc itself, which is the technique of this chapter applied to the problem it causes:
rename proc _real_proc
_real_proc proc {name params body} {
if {[llength [info procs $name]] > 0} {
puts " WARNING: redefining '$name'"
}
_real_proc $name $params $body
}
proc demo {} { return 1 }
proc demo {} { return 2 }
rename proc {}
rename _real_proc proc WARNING: redefining 'demo'Switch that on while loading a project and every duplicate announces itself. The last two lines put proc back, which matters — leaving a wrapper on proc in place would warn for every later definition too.
8. When to use this
Reasonable uses, roughly in order of how often they are justified:
- Temporary instrumentation while debugging — log calls, count them, time them, then remove the wrapper.
- Adding a check to code you cannot edit, such as a third-party library.
- Test doubles — replace a command that talks to something external with one that returns fixed data.
- Permanent behaviour changes, which are almost always better done by changing the call sites.
9. Exercises
Exercise 1. What does rename foo {} do?
Exercise 2. What happens if you rename a b when b already exists?
Exercise 3. Write a wrapper for a procedure compute that prints its arguments before calling it and still returns the original result.
Exercise 4. Why does the wrapper use args and {*}$args rather than naming the parameters?
Exercise 5. Two files both define proc check. What does info procs check report, and how do you find out which definition is live?
Exercise 6. Give one good use of wrapping a built-in and one reason to be cautious.
Answers
1. It deletes the command. Afterwards info procs foo is empty and calling it gives invalid command name "foo".
2. It fails with can't rename to "b": command already exists. The rename is refused, so you cannot overwrite a command this way by accident.
3.
proc compute {x y} { return [expr {$x + $y}] }
rename compute compute_real
proc compute {args} {
puts " compute called with: $args"
return [compute_real {*}$args]
}
puts [compute 2 3] compute called with: 2 3
54. So the wrapper does not depend on the wrapped command's signature. It accepts whatever it is given and forwards it unchanged, which means it keeps working if the original gains or loses a parameter — and the same wrapper shape works for any command.
5. info procs check reports one — there is a single command and it is whichever definition loaded last. To find out which, run info body check and compare it with the files; the live body is the one that is really running.
6. A good use: temporarily wrapping a built-in to log or count its calls while debugging, then removing the wrapper. A reason for caution: the wrapper affects every caller in the interpreter, including library code you have not read, so a small mistake in it surfaces as a bug somewhere unrelated.
10. Remember
rename old newmoves a command;rename old {}deletes it.- Renaming onto an existing name is refused —
can't rename to "x": command already exists. - The wrapper pattern is: rename away, redefine taking
args, forward with{*}$args, return the result. No call site changes. - Using
argsmeans the wrapper does not depend on the wrapped command's signature. - It works on built-ins, and is reversible — delete the replacement, rename the original back.
- Wrapping a built-in affects every caller in the interpreter, including code you have not read.
- The same mechanism makes an accidental redefinition invisible: one command, last definition wins, nothing recorded.
info body nameshows the definition that is actually live — the first thing to check when a procedure misbehaves and its source looks right.- A
define_onceguard, or a temporary wrapper onprocitself, turns silent redefinition into a message.
Where this fits
Part of the Tcl curriculum.
