Skip to content
VLSI Mentor

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:

Nothing in this chapter is a new capability. It is those three used deliberately.

2. Renaming

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
proc helper {} { return "helped" }

rename helper helper_original

puts "helper still defined? [llength [info procs helper]]"
puts [helper_original]
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
helper still defined? 0
helped

The 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:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
proc one {} { return 1 }
proc two {} { return 2 }
rename one two
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
can't rename to "two": command already exists

3. Deleting

Renaming to the empty string removes a command:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
proc temporary {} { return "here" }
puts "before: [llength [info procs temporary]]"

rename temporary {}
puts "after : [llength [info procs temporary]]"
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
before: 1
after : 0

4. The wrapper

Put those together and you have the pattern:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
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]
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
  (calling fetch with: 42)
  (fetch returned: record-42)
record-42

Every 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

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
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}]
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
300
3

The 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:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
# 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]]"
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
B says hello
definitions registered: 1

There 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:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
proc check {x} { return "B says $x" }
puts [string trim [info body check]]
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
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:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
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"
}
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
1
rejected: procedure 'safe' is already defined

Note 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:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
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
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
  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.

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
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]
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
  compute called with: 2 3
5

4. 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 new moves 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 args means 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 name shows the definition that is actually live — the first thing to check when a procedure misbehaves and its source looks right.
  • A define_once guard, or a temporary wrapper on proc itself, turns silent redefinition into a message.

Where this fits

Part of the Tcl curriculum.