Modules & Packages
Prerequisites: Level 1 — Beginner
In Level 1 every program lived in a single main.go. Real projects are split
into packages, and packages are grouped into a module.
- A package is a directory of
.gofiles that all start with the samepackageclause. Everything in that directory can see everything else in it. - A module is a tree of packages versioned together. Its root holds a
go.modfile that names the module path and the Go version. - A name is exported (visible to other packages) when it starts with a
capital letter:
greet.Hellois exported,greet.helperis not. There are nopublicorprivatekeywords. - A package inside a directory named
internal/can only be imported by code in the directory tree rooted at the parent ofinternal/. Use it for code you don't want other modules to depend on.
Syntax
go mod init example.com/hello # create go.mod
go get github.com/google/uuid@latest # add or upgrade a dependency
go mod tidy # add missing / remove unused requirements
An import path is the module path followed by the package's directory:
hello/
├── go.mod module example.com/hello
├── main.go package main
├── greet/
│ └── greet.go import "example.com/hello/greet"
└── internal/
└── format/
└── format.go import "example.com/hello/internal/format"
Example
go.mod:
module example.com/hello
go 1.23
internal/format/format.go:
package format
// Exclaim adds an exclamation mark to s.
func Exclaim(s string) string {
return s + "!"
}
greet/greet.go:
package greet
import "example.com/hello/internal/format"
// defaultName is unexported: only code in package greet can use it.
const defaultName = "Gopher"
// Hello is exported because its name starts with a capital letter.
func Hello(name string) string {
if name == "" {
name = defaultName
}
return format.Exclaim("Hello, " + name)
}
main.go:
package main
import (
"fmt"
"example.com/hello/greet"
)
func main() {
fmt.Println(greet.Hello("Ada"))
fmt.Println(greet.Hello(""))
}
Run it from the module root with go run . (the dot means "the package in
this directory").
Expected output
Hello, Ada!
Hello, Gopher!
When you go get a third-party package, Go records the requirement in
go.mod and writes checksums to go.sum. Commit both files.
Common mistakes
- Trying to use a lowercase name from another package, such as
greet.defaultName. It is not exported, so the compiler reportsundefined: greet.defaultName. - Using the directory name alone in an import (
import "greet"). Imports of your own packages always start with the module path fromgo.mod. - Creating an import cycle (package
aimportsbandbimportsa). Go refuses to compile cycles; move the shared code into a third package. - Putting more than one package name in the same directory. Every file in a
directory must use the same
packageclause (test files are the one exception, covered later).
Practice
In the hello module, add a package mathx with an exported function
Double(n int) int, then call it from main.go to print 42.
Show solution
mathx/mathx.go:
package mathx
// Double returns n multiplied by two.
func Double(n int) int {
return n * 2
}
main.go:
package main
import (
"fmt"
"example.com/hello/mathx"
)
func main() {
fmt.Println(mathx.Double(21))
}