Explain exported vs. unexported identifiers in Go.
Quick Answer
Go has no public/private keywords. Visibility is determined entirely by capitalization: any identifier (function, type, variable, struct field, method) starting with an uppercase letter is exported — visible and usable from other packages. One starting with a lowercase letter is unexported — visible only within its own package. This applies uniformly to package-level declarations and struct fields, so a struct can mix exported and unexported fields to control what's part of its public API. There's no partial visibility like Java's protected; it's a simple binary rule enforced by the compiler, based purely on the name's first letter.
Detailed Answer
Go's visibility rule is unusually simple: capitalize to export, lowercase to keep private to the package.
The rule, applied everywhere
package account
type Account struct {
Balance float64 // exported — usable as account.Account{}.Balance from other packages
secret string // unexported — only accessible within package account
}
func NewAccount() *Account { ... } // exported constructor
func (a *Account) validate() error { ... } // unexported helper method
package main
import "myapp/account"
func main() {
a := account.NewAccount()
fmt.Println(a.Balance) // OK, exported
fmt.Println(a.secret) // compile error: secret is unexported
}
Why this design, instead of keywords
Capitalization-based visibility means you can tell an identifier's accessibility just by reading its name, with no need to check a separate modifier keyword. It also nudges API design. A well-designed package exposes a small set of capitalized names as its public surface. Everything else stays lowercase, free to change later without breaking callers.
Struct field visibility and JSON
type User struct {
Name string `json:"name"`
email string // unexported: encoding/json can't see it via reflection, so it's silently skipped
}
The encoding/json package (and most reflection-based libraries) can only read exported fields, since Go's reflection API itself respects the same visibility rule. An unexported field is invisible to json.Marshal, which is a common source of confusion when a field mysteriously never appears in serialized output.