Packages & Modules
Imports, visibility, init, go mod, version management
Packages
Each directory is one package; every .go file in a directory must share the same package name. An executable's entry package must be named main and contain func main(). A library package name is usually the same as its directory name.
myapp/
├── go.mod
├── main.go // package main
└── internal/
└── store/
├── store.go // package store
└── store_test.go // package storeVisibility
An identifier is exported (visible outside the package) when its first letter is uppercase, and package-private when lowercase — struct fields follow the same rule. The package is Go's smallest encapsulation unit; there are no public/private keywords.
// store/user.go
package store
func parse(s string) {} // package-private
func Parse(s string) {} // exported
type User struct {
ID int // readable/writable outside the package
name string // private to the package
}Imports
import (
"fmt" // standard library
"example.com/myapp/internal/db" // within the same module
"github.com/google/uuid" // third-party
alias "encoding/json" // alias (when names collide)
. "math" // dot import — symbols into current scope (not recommended outside tests)
_ "github.com/lib/pq" // blank import — only triggers init
)init Functions and Execution Order
Each file can define any number of init() functions, run in filename order; a package's init runs before main(). The init order across packages follows dependencies: a depended-on package initializes first. The blank import `_ "github.com/lib/pq"` relies on exactly this side effect — driver registration.
package main
import "fmt"
type Driver interface {
Name() string
}
type MemoryDriver struct{}
func (MemoryDriver) Name() string { return "memory" }
var registry = map[string]Driver{}
func Register(name string, d Driver) { registry[name] = d }
func init() {
Register("memory", MemoryDriver{})
}
func main() {
fmt.Println(registry["memory"].Name())
}Go Modules
$ go mod init example.com/myapp # create a module (example.com/myapp is the import-path prefix)
$ go get github.com/google/uuid # add the latest dependency
$ go get github.com/google/uuid@v1.6.0 # pin a version
$ go get -u ./... # upgrade all dependencies of this module
$ go mod tidy # sync go.mod / go.sum: drop unused, add missing
$ go mod download # download into the local cache
$ go mod why github.com/x/y # explain why this is a dependency
$ go mod graph # dependency graphgo.mod Structure
module example.com/myapp
go 1.22
require (
github.com/google/uuid v1.6.0
golang.org/x/sync v0.7.0
)
replace example.com/lib => ../lib // for local debugging
exclude github.com/bad/pkg v1.2.3 // exclude a versionSemantic Versioning and /v2
Go Modules strictly follow SemVer: vMAJOR.MINOR.PATCH, and for v2 and above the module path must change to module example.com/lib/v2, published as a v2 subdirectory or on the main branch. You import example.com/lib/v2 too — otherwise it's a different module.
internal and Directory Layout
Packages under an internal directory can only be imported within the same module — access control enforced by the Go compiler. A common "standard layout":
myapp/
├── cmd/
│ └── api/main.go // executable entry
├── internal/ // usable only within this module
│ ├── http/
│ ├── service/
│ └── store/
├── pkg/ // reusable utility libs for external use (optional)
├── api/ // proto / openapi
├── go.mod
└── go.sumGOPROXY and Private Modules
Go downloads dependencies through GOPROXY by default. In China goproxy.cn is commonly configured; for a company's private repos, add the matching paths to GOPRIVATE so the go command skips the proxy and the checksum database.
$ go env -w GOPROXY=https://goproxy.cn,direct
$ go env -w GOSUMDB=sum.golang.google.cn
$ go env -w GOPRIVATE=*.corp.example.com,git.internal.ioworkspace (Multi-Module Development)
Go 1.18+ provides go.work so you can develop several local modules at once without editing replace in each.
$ go work init ./api ./lib
$ go work use ./tools # add one more