Explain exported vs. unexported identifiers in Go.

4 minbeginnergopackagesvisibilityfundamentals

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.