Tcl Part I · Module 10 · Procedures and Reuse
Local Variables: What a Procedure Can See
This is the chapter that catches people, and it catches them hardest if they come from shell scripting — where every variable is global by default and a function can read whatever the script set before calling it. Tcl works the other way round. A procedure gets a fresh, empty set of variables, and names from the calling scope are simply not there. What makes the rule worth real attention is that it does not apply uniformly: your procedure can call any command defined anywhere, while it cannot read a variable set one line above the call. Understanding that asymmetry explains almost everything else about how Tcl procedures behave.
Foundation12 min readTclPart IProceduresScopeLocal variables
Part I · Module 10 · Chapter 10.6 · Local Variables: What a Procedure Can See
1. A procedure cannot see the caller's variables
set total 10
proc show {} {
return $total
}
showcan't read "total": no such variabletotal is right there, one line above. The procedure cannot see it.
When a procedure runs, Tcl creates a new frame — a fresh, empty set of variables. The only things in it are the parameters, bound to the arguments. Everything else the procedure uses it must create itself.
2. Reaching the global one, on purpose
Two ways. global declares that a name refers to the global variable:
set total 10
proc show_global {} {
global total
return $total
}
puts [show_global]10Or name it explicitly with the :: prefix, which needs no declaration:
proc show_qualified {} {
return $::total
}
puts [show_qualified]103. Locals do not leak out either
The barrier runs both ways:
proc sets_a_local {} {
set inside 42
}
sets_a_local
puts "inside exists afterwards? [info exists inside]"inside exists afterwards? 0The procedure's frame is discarded when it returns. Nothing it set survives unless it was returned, or written to a global on purpose.
This is what makes procedures safe to use: calling one cannot quietly change your variables, so you do not have to know what names it uses internally.
4. The same name in two frames is two variables
set v "outer"
proc shadow {} {
set v "inner"
return $v
}
puts "procedure returned : [shadow]"
puts "outer afterwards : $v"procedure returned : inner
outer afterwards : outerTwo completely separate variables that happen to share a name. The procedure did not shadow the global in the sense of temporarily hiding it — it never had access to it at all, and created its own.
5. Parameters are just locals
proc bump {n} {
incr n
return $n
}
set count 10
puts "returned : [bump $count]"
puts "original : $count"returned : 11
original : 10n is a local initialised from the argument. Changing it changes nothing outside — the behaviour from Chapter 10.2 §6, now explained: it is not that arguments are special, it is that n lives in a frame that is about to be thrown away.
6. Commands are global; variables are local
Here is the asymmetry that makes the rule make sense. Your procedure cannot see a variable from the calling scope, but it can call any command defined anywhere:
set secret 99
proc helper {} {
return "helped"
}
proc caller {} {
# this works — commands live in one global namespace
set a [helper]
# this would fail — variables do not
# return $secret
return $a
}
puts [caller]helpedThere is one table of commands, shared by everything, which is why Chapter 10.1 could define outer before inner existed and why redefining a procedure affects every caller at once. There is a separate set of variables per frame, which is why $secret is invisible.
7. Prefer parameters and return values
global works and is usually the wrong tool. Compare:
# global state — the procedure's behaviour depends on something invisible
set threshold 10
proc over_limit_global {values} {
global threshold
set n 0
foreach v $values { if {$v > $threshold} { incr n } }
return $n
}
puts [over_limit_global {5 15 20}]
# explicit — everything it needs is in the call
proc over_limit {values threshold} {
set n 0
foreach v $values { if {$v > $threshold} { incr n } }
return $n
}
puts [over_limit {5 15 20} 10]
puts [over_limit {5 15 20} 18]2
2
1The second version can be called twice with different thresholds on adjacent lines. The first cannot without mutating a global between calls, and a reader has to look elsewhere to know what it will do.
8. What this chapter is not covering
Three related mechanisms belong to Module 11 and are mentioned here only so you know they exist:
upvarlinks a local name to a variable in the caller's frame — the mechanism arrays need in order to cross a procedure boundary, from Chapter 7.1 §6.uplevelruns a script in the caller's frame.- Namespaces give you more than the two levels — global and local — that this chapter has assumed.
Everything above holds regardless; those three are additions, not corrections.
9. Exercises
Exercise 1. What exactly does this print, and why?
set x 1
proc f {} { return $x }
fExercise 2. Give two ways to make f above read the global x.
Exercise 3. After proc g {} { set y 5 } is called, does y exist at the top level? Why?
Exercise 4. Explain why a procedure can call another procedure but cannot read the caller's variable.
Exercise 5. Rewrite this to take the limit as a parameter, and say what improves:
set limit 100
proc count_over {values} {
global limit
...
}Exercise 6. Name one case where global is the right choice.
Answers
1. can't read "x": no such variable. The procedure runs in a fresh frame containing only its parameters, and x is not among them — the global x is not visible.
2.
proc f1 {} { global x ; return $x }
proc f2 {} { return $::x }3. No. The procedure's frame, including y, is discarded when it returns. info exists y at the top level is 0.
4. Because commands and variables are kept differently. There is a single global table of commands that every frame can reach, so any procedure can call any other. Variables live in a per-frame table that is private to the running procedure, so a name set in the caller's frame is simply not present in the callee's.
5.
proc count_over {values limit} {
set n 0
foreach v $values { if {$v > $limit} { incr n } }
return $n
}
puts [count_over {50 150 250} 100]
puts [count_over {50 150 250} 200]2
1It can now be called with different limits without changing anything outside it, its behaviour is fully described by its call, and it can be tested without setting up a global first.
6. Anything genuinely global and set once: a configuration dictionary built at start-up, a verbosity flag, or one of Tcl's own globals such as env and tcl_platform from Chapter 7.8.
10. Remember
- A procedure runs in a fresh frame containing only its parameters. It cannot see the caller's variables.
can't read "x": no such variableis what that looks like, and it is the first thing shell programmers hit.global namedeclares that a name refers to the global variable;$::namedoes the same inline with no declaration.- Locals do not leak out — the frame is discarded on return, so calling a procedure cannot change your variables.
- The same name in two frames is two variables, not one hidden behind the other.
- Parameters are just locals initialised from the arguments, which is why modifying one cannot affect the caller.
- Commands are global and shared; variables are local and private. That one asymmetry explains both silent redefinition and
no such variable. - Prefer parameters and return values to
global— a procedure whose inputs are all parameters can be read, reused and tested on its own.
Where this fits
Part of the Tcl curriculum.
