Scope and Shadowing
Here is a bug nearly every Go programmer writes once. tally counts the words, prints the total — and prints 0. The counting worked; the program just printed a different variable with the same name.
The rule
A name lives until its closing brace; an inner
:=makes a new one.
Scope is the region of code where a name can be used. In Go it follows the braces:
- Package scope: declared outside any function, visible in every file of the package.
- Function scope: parameters and variables declared in a function body.
- Block scope: variables declared inside
{ }of anif,for,switchor a bare block, plus variables declared in the header of those statements, such asif err := check(); err != nil.
A name is visible from where it is declared to the end of its block, including all the blocks nested inside.
Shadowing happens when an inner block declares the same name again. The inner variable hides the outer one until the inner block ends:
total := 0
if len(words) > 0 {
total := len(words) // new variable, only inside this block
fmt.Println(total) // 5
}
fmt.Println(total) // 0Nothing is wrong with the syntax, so the compiler is happy. The fix is a single character: delete the colon, so the line reads total = len(words).
Short-lived scope is also why the if header form is so handy:
if n, err := countFile(path); err != nil {
return err
}
// n and err do not exist hereThe variables vanish after the if, so they cannot clash with anything later.
The shortcut
When a value "didn't change", find every := of that name and draw its braces. If the one you assigned to lives in a smaller box than the one you print, you shadowed it.
Find the declarations
total :=appears twice: once inmain, once inside theif.
Two more places the same trap hides: a := inside a for loop body, and err redeclared inside a nested block so the outer err is never set. Linters, such as the shadow analyzer, can flag these.
Drills
Answer fast — trust the rule.
What does x := 1; { x := 2; fmt.Print(x) }; fmt.Print(x) print?
x := 1; { x = 2 }; fmt.Println(x) — what prints?
After if n, err := count(); err == nil { ... }, can the next line use n?
Why does the compiler not stop the shadowing bug by itself?
Remember
- A name lives until its closing brace; an inner
:=makes a new one. - Shortcut: find every
:=of the name and draw its braces. - Trap: an inner
:="updates" nothing — use=to change the outer variable.
