Exported and Unexported Names
A capital first letter exports a name to other packages; a lowercase one keeps it inside, and the package is the privacy boundary.
func Exported() {} // usable from other packages; func unexported() {} // same package onlyGo has no public or private keyword. The first letter of a name decides who may use it. A name whose first letter is uppercase is exported: other packages can use it. A name whose first letter is lowercase is unexported: only code in the same package can use it.
The rule applies to names declared at the top level of a package, and to struct field names and method names. A name declared inside a function is never reachable from outside that function, whatever its first letter.
The package is the boundary
Unexported means private to the package, not private to the file. Every file in the package can read and write every lowercase name in it. So a package is the unit of encapsulation: you choose what crosses its edge, and the compiler enforces that choice.
What to export
Export the smallest set of names that lets callers do their work. Keep a struct's fields unexported and offer methods when the fields must stay consistent with each other. Renaming an exported name breaks every caller; renaming an unexported one is a local edit you can make in an afternoon.
What trips people up
Libraries that inspect values at run time can only see exported names. encoding/json is the common case: it writes exported fields and skips unexported ones without complaining. A field you spelled name instead of Name simply never appears.
Example
package main
import "fmt"
// Counter is exported: other packages may name this type.
// Its field count is not, so only this package can change it.
type Counter struct {
count int
}
// Add is exported, so callers outside the package can use it.
// A method on *Counter can change the value it was called on.
func (c *Counter) Add(n int) {
c.count += n
}
// Total is the exported way to read the hidden field.
func (c *Counter) Total() int {
return c.count
}
func main() {
var c Counter
c.Add(2)
c.Add(40)
// c.count would compile here, because main is in the same package.
// From another package, only Add and Total exist.
fmt.Println("total:", c.Total())
}
// Output:
// total: 42When to use it
- Deciding which names a new package should expose before other code starts depending on it.
- Hiding a struct's fields so callers cannot leave an object half-built.
- Finding out why a struct field never shows up in your JSON output.
- Keeping a token or password in a field that cannot be serialised by mistake.
More examples
A package that keeps its helper to itself
The exported surface is New, Put and Get. Because items and normalise are unexported, no caller can bypass the key handling or leave the map in a shape the package did not build. This is the normal shape for a package you will keep changing: a small exported API over a larger private one.
// internal/store/store.go
package store
import "strings"
// Store is exported, and so is Put, so other packages can use them.
type Store struct {
items map[string]string // unexported: nobody outside can reach in
}
func New() *Store {
return &Store{items: map[string]string{}}
}
func (s *Store) Put(key, value string) {
s.items[normalise(key)] = value
}
func (s *Store) Get(key string) (string, bool) {
v, ok := s.items[normalise(key)]
return v, ok
}
// normalise is lowercase, so it is invisible outside this package.
// Both Put and Get use it, so keys are treated the same way everywhere.
func normalise(key string) string {
return strings.ToLower(strings.TrimSpace(key))
}JSON only sees exported fields
Shows the one case where capitalisation changes run-time behaviour rather than just visibility. It is also a useful tool: a secret kept in an unexported field cannot leak into a response body by accident. Check this first when a field is mysteriously missing from an API response.
package main
import (
"encoding/json"
"fmt"
)
type User struct {
Name string // exported: appears in the JSON
Email string // exported
token string // unexported: skipped, with no error
}
func main() {
u := User{Name: "Ada", Email: "[email protected]", token: "secret"}
b, err := json.Marshal(u)
if err != nil {
fmt.Println("marshal:", err)
return
}
fmt.Println(string(b))
}
// Output:
// {"Name":"Ada","Email":"[email protected]"}Capitals do not help inside a function
A reminder that the uppercase rule is about where a name is declared, not only about its spelling. Worth knowing when you read a review comment asking you to move a value out of a function so that other packages can configure it.
package demo
// Total is declared at package level and exported.
var Total int
func Run() {
// Count is capitalised but declared inside a function.
// It is not exported, and no other package can ever see it:
// export applies to the package block, struct fields and methods.
Count := 7
Total += Count
}
Discussion