Packages & Modules
go.mod, internal/, exported names, the toolchain.
Packages & Modules
Go's unit of compilation is the package. A directory is a package. A repo full of packages is a module, declared by a go.mod at the root.
Package basics
Every Go file starts with a package declaration:
package auth
import (
"errors"
"fmt"
)
func Login(name, password string) (string, error) {
if name == "" || password == "" {
return "", errors.New("missing credentials")
}
return fmt.Sprintf("session:%s", name), nil
}
All files in the same directory must declare the same package name. The package name is what consumers import:
import "myapp/auth"
token, err := auth.Login("alice", "...")
Exported vs unexported
Identifiers starting with a capital letter are exported (visible to other packages). Lowercase = package-private.
package auth
func Login(...) (...) // exported
func validate(...) (...) // unexported — only auth/*.go can see it
type User struct { ... } // exported type
type session struct { ... } // unexported type
No public/private keywords. Visibility is encoded in the name.
Importing
import (
"fmt" // stdlib
"net/http" // stdlib subpackage
"github.com/google/go-cmp/cmp" // third-party
"myapp/auth" // local
log "github.com/sirupsen/logrus" // alias the import
_ "github.com/lib/pq" // import for side effects (e.g. driver register)
)
The blank import (_) runs the package's init() for its side effects without binding any names. SQL drivers and image codecs use this pattern.
Modules and go.mod
A module is a directory tree with a go.mod at the root:
// go.mod
module github.com/myname/myapp
go 1.22
require (
github.com/google/go-cmp v0.6.0
github.com/lib/pq v1.10.9
)
go.mod is the manifest: module path + Go version + dependencies. go.sum is a checksum database that pins exact versions for reproducible builds.
Common workflow
go mod init github.com/myname/myapp # create go.mod
go get github.com/lib/pq # add a dependency
go get -u # upgrade all
go mod tidy # add missing, drop unused
go build ./... # build every package
go test ./... # test every package
go install # build + install the binary
./... is shorthand for "the current dir and every subdirectory" — the canonical way to operate on a whole module.
Internal packages
A package under any path containing a directory named internal/ can only be imported by packages rooted at the parent of that internal/:
myapp/
cmd/server/
internal/
secrets/ # only myapp/* can import this
pkg/
public/ # anyone can import this
It's the closest Go gets to enforced visibility scoping above the package level.
main package
The package named main produces an executable. It must have a main() function:
package main
import "fmt"
func main() {
fmt.Println("hello")
}
By convention these live under cmd/<name>/main.go so a single repo can ship multiple binaries.
init() once per file
Every file may have a func init() that runs once when the package is loaded — before main(). Multiple init() functions across the package run in undefined order, so don't depend on order between them. Use sparingly — explicit setup at the call site is easier to test.
Don't import "main" or cycles
main cannot be imported by other packages. And Go forbids import cycles entirely — A imports B imports A is a compile error. Cycles usually point at a bad split: one of the things should live in a third package both depend on, or you should merge them.