Skip to content
VLSI Mentor

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

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
set total 10

proc show {} {
    return $total
}

show
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
can't read "total": no such variable

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

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
set total 10

proc show_global {} {
    global total
    return $total
}
puts [show_global]
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
10

Or name it explicitly with the :: prefix, which needs no declaration:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
proc show_qualified {} {
    return $::total
}
puts [show_qualified]
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
10
Two parallel left-to-right flows. Without a global declaration a procedure reads a name in its own frame only, the name is unbound, and the result is an error saying no such variable. With a global declaration the name is bound to the global variable and the procedure reads its value.Without globalthe body reads a nameOwn frame onlythe name is unboundErrorno such variableWith globaldeclared in the bodyName boundto the global oneReads 10the global value12
Figure 1 — why one of these works and the other does not. A procedure body that simply names a variable looks only in its own frame, finds nothing bound to that name, and fails. Declaring the name global, or qualifying it with a leading double colon, binds it to the global variable instead, and the read then succeeds. Nothing about the surrounding code changes; only the procedure's declaration does.

3. Locals do not leak out either

The barrier runs both ways:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
proc sets_a_local {} {
    set inside 42
}
sets_a_local

puts "inside exists afterwards? [info exists inside]"
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
inside exists afterwards? 0

The 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

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
set v "outer"

proc shadow {} {
    set v "inner"
    return $v
}

puts "procedure returned : [shadow]"
puts "outer afterwards   : $v"
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
procedure returned : inner
outer afterwards   : outer

Two 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

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
proc bump {n} {
    incr n
    return $n
}

set count 10
puts "returned : [bump $count]"
puts "original : $count"
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
returned : 11
original : 10

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

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

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

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

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

  • upvar links 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.
  • uplevel runs 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?

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
set x 1
proc f {} { return $x }
f

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

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

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

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

It 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 variable is what that looks like, and it is the first thing shell programmers hit.
  • global name declares that a name refers to the global variable; $::name does 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.