Skip to content
VLSI Mentor

Tcl Part I · Module 11 · Scope and Namespaces

Qualified Names and How Tcl Resolves Them

Namespaced names look like file paths and behave like them: a leading double colon means start from the root, and anything else means start from where you are. That much is easy. The part worth a chapter is what happens when a name has no namespace on it at all, because Tcl's answer depends on whether the name is a command or a variable — and the two rules are different. Knowing which is which explains why puts works unqualified inside a namespace while a global variable in the same procedure does not, and it turns most namespace confusion into something you can reason about rather than guess at.

Intermediate11 min readTclPart INamespacesName resolutionScope

Part I · Module 11 · Chapter 11.8 · Qualified Names and How Tcl Resolves Them

1. Absolute and relative

A name containing :: is qualified. A leading :: makes it absolute — resolved from the root, wherever you happen to be:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
namespace eval outer {
    namespace eval inner {
        proc who {} { return "outer::inner::who" }
    }

    proc call_absolute {} { return [::outer::inner::who] }
    proc call_relative {} { return [inner::who] }
}

puts [outer::call_absolute]
puts [outer::call_relative]
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
outer::inner::who
outer::inner::who

Both reached the same procedure. The difference is where they started from: ::outer::inner::who from the root, inner::who from whatever namespace the calling procedure is in — which here is ::outer.

Run the relative one from the global scope and it fails, because there is no top-level namespace called inner:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
inner::who
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
invalid command name "inner::who"

2. Where am I?

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
namespace eval probe {
    proc here {} { return [namespace current] }
}

puts "inside : [probe::here]"
puts "at top : [namespace current]"
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
inside : ::probe
at top : ::

The global namespace is called ::, which is why a global variable's absolute name is $::name — that is just the empty namespace path followed by the variable.

3. The asymmetry

Now the part that matters. When a name has no :: in it, Tcl resolves commands and variables by different rules.

A command not found in the current namespace is looked up in the global one:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
proc global_command {} { return "found the global command" }

namespace eval ns1 {
    proc try_command {} { return [global_command] }
}
puts [ns1::try_command]
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
found the global command

A variable is not:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
set global_variable "the global value"

namespace eval ns2 {
    proc try_variable {} { return $global_variable }
}
ns2::try_variable
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
can't read "global_variable": no such variable

Same situation, opposite outcome. Qualify it and it works:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
namespace eval ns3 {
    proc try_qualified {} { return $::global_variable }
}
puts [ns3::try_qualified]
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
the global value

4. The command lookup order

For an unqualified command name, Tcl looks:

  1. in the current namespace, then
  2. in the global namespace.

Which is why a namespace can shadow a global command for its own calls:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
proc describe {} { return "the global one" }

namespace eval shadowy {
    proc describe {} { return "the namespace one" }
    proc call {} { return [describe] }
}

puts "inside  : [shadowy::call]"
puts "outside : [describe]"
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
inside  : the namespace one
outside : the global one

shadowy::call found describe at step 1 and never reached step 2. Code outside the namespace never sees shadowy's version at all.

5. Variables: declare or qualify

Because variable lookup does not fall back, a namespace procedure has exactly three ways to reach a variable that is not its own local:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
set ::top_level "global"

namespace eval v {
    variable own "namespace"

    proc by_variable {}  { variable own ; return $own }
    proc by_namespace {} { return $::v::own }
    proc by_global {}    { return $::top_level }
}

puts "variable decl : [v::by_variable]"
puts "qualified ns  : [v::by_namespace]"
puts "qualified ::  : [v::by_global]"
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
variable decl : namespace
qualified ns  : namespace
qualified ::  : global
To reachUse
a variable in this namespacevariable name, then the short name
a variable in this namespace, once$::thisns::name
a global variableglobal name, or $::name

There is no fourth option and no automatic search. A bare name in a procedure means a local, always.

6. namespace which

When a name is not resolving the way you expect, namespace which tells you what it actually resolves to:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
namespace eval w {
    proc target {} {}
    variable thing 1

    proc report {} {
        return [list [namespace which -command target] [namespace which -variable thing]]
    }
}

puts [w::report]
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
::w::target ::w::thing

It returns the fully-qualified name, or the empty string if the name does not resolve at all. That makes it the fastest way to answer "which describe is this call going to get?" without reading the whole script.

7. A worked example

A library that deliberately uses all three forms, with a comment saying why each:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
set ::verbose 0

namespace eval fmt {
    variable width 24

    proc set_width {n} {
        variable width            ;# our own state
        set width $n
    }

    proc line {name value} {
        variable width            ;# our own state
        # `format` is unqualified: command lookup falls back to global
        return [format "  %-*s %s" $width $name $value]
    }

    proc render {rows} {
        set out {}
        foreach {k v} $rows {
            lappend out [line $k $v]   ;# a sibling, short name is fine
        }
        if {$::verbose} {              ;# a genuine global, qualified
            lappend out "  ([llength $out] rows at width $::fmt::width)"
        }
        return [join $out "\n"]
    }
}

puts [fmt::render {cycles 12000 errors 0}]

set ::verbose 1
fmt::set_width 12
puts [fmt::render {cycles 12000 errors 0}]
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
  cycles                   12000
  errors                   0
  cycles       12000
  errors       0
  (2 rows at width 12)

Three kinds of name in one small library: a namespace variable declared with variable, a global command used unqualified, and a global variable qualified with ::.

8. Exercises

Exercise 1. What is the difference between ::a::b and a::b?

Exercise 2. Why does inner::who work inside outer but fail at the top level?

Exercise 3. State the lookup rule for an unqualified command and for an unqualified variable.

Exercise 4. Why does puts work unqualified inside a namespace while a global variable does not?

Exercise 5. A library namespace defines open. One of its procedures calls open $path r meaning Tcl's file command. What happens, and how do you fix it?

Exercise 6. Give the three ways a namespace procedure can reach a variable that is not its own local.

Answers

1. ::a::b is absolute — resolved from the root namespace regardless of where it appears. a::b is relative — resolved starting from the current namespace, so it means different things in different places.

2. Because relative names resolve from the current namespace. Inside outer, inner::who means ::outer::inner::who. At the top level it means ::inner::who, and no top-level namespace called inner exists — so Tcl reports invalid command name "inner::who".

3. An unqualified command is looked up in the current namespace and then, if not found, in the global namespace. An unqualified variable is looked up only in the current procedure's local frame — there is no fallback at all.

4. Because puts is a command and commands fall back to the global namespace when they are not found locally. A variable name has no such fallback, so a bare name in a procedure can only ever mean a local one.

5. It gets the library's own open, because command lookup finds the current namespace first and never reaches the global one. Fix it by qualifying: ::open $path r.

6. Declare it with variable name and use the short name; qualify it as $::thisns::name; or, for a genuine global, declare it with global name or qualify it as $::name.

9. Remember

  • A leading :: makes a name absolute, resolved from the root; anything else is relative to the current namespace.
  • The analogy to file paths holds: ::a::b is /a/b, a::b is a/b, and namespace current is pwd.
  • The global namespace is called ::, which is why a global variable is $::name.
  • Command lookup falls back to the global namespace; variable lookup does not. This is the one rule to remember.
  • So a namespace can shadow a global command for its own calls — qualify with ::name when you mean the built-in.
  • A bare variable name in a procedure always means a local. To reach anything else: variable, global, or a qualified name.
  • namespace which -command and -variable report what a name actually resolves to, or empty if it does not.

Where this fits

Part of the Tcl curriculum.