Modules and Dependencies

How go get, minimal version selection, go.sum and go mod tidy decide which version of each dependency your build uses.

Syntaxgo get example.com/[email protected] # then: go mod tidy

A module is a group of packages released and versioned together. It is the unit of dependency: your module depends on other modules at chosen versions. A module is a directory tree with a go.mod file at its root. That file holds the module's own path, the Go version the code is written for, and one require line per dependency. Modules became the standard way to build Go code in Go 1.16, when they were turned on by default.

Adding and choosing versions

go get example.com/[email protected] edits go.mod to require that version. @latest asks for the newest release. Since Go 1.18, go get only adjusts dependencies; to install a program use go install example.com/cmd@latest.

Your dependencies have dependencies of their own, so two of them may require different versions of the same module. Go settles this with minimal version selection: the version built is the highest one that any module in the graph requires, and nothing newer. A newer release appearing on the server changes nothing until something requires it, so your build keeps producing the same result until you change go.mod.

go.sum and tidy

go.sum records a hash for each module version you use: one for its contents, one for its go.mod. Downloads are checked against those hashes, so content that changed under a published version fails the build instead of running. Commit both files.

go mod tidy adds every requirement your code imports, removes ones nothing imports, and brings go.sum in line. Run it before you commit.

Example

Example · go
// This file is main.go in a module created with:
//
//	go mod init example.com/greet
//
// That writes a go.mod holding two lines:
//
//	module example.com/greet
//	go 1.26.0
//
// Since Go 1.26, go mod init records one release below the toolchain you
// ran it with, so a Go 1.27 toolchain writes go 1.26.0. Follow it with
// go get [email protected] if the code needs the newer language version.
//
// There are no require lines, because fmt and strings ship with Go.
// The standard library is not a dependency and never appears in go.mod.
package main

import (
	"fmt"
	"strings"
)

func main() {
	parts := []string{"go", "mod", "tidy"}
	fmt.Println(strings.Join(parts, " "))
}

// Output:
// go mod tidy

When to use it

  • Pinning a dependency to one exact version and proving later which version a release was built with.
  • Explaining why go get on one library also raised the version of three others.
  • Fixing a CI failure that says there is a missing go.sum entry.
  • Dropping a library you stopped importing so it is no longer downloaded or scanned.

More examples

Adding a dependency, start to finish

The whole loop for taking on a dependency. The important habit is the tidy step: without it, go.mod drifts away from what the code actually imports, and a colleague's first build fails on a missing go.sum entry.

Example · bash
# Start a module. The path is how other code would import it.
go mod init example.com/app

# Ask for one version of one module.
go get github.com/google/[email protected]

# go.mod now reads:
#   module example.com/app
#
#   go 1.26.0
#
#   require github.com/google/uuid v1.6.0

# Since Go 1.26, go mod init records one release below the toolchain that
# ran it, so a Go 1.27 toolchain writes go 1.26.0. To move the module to
# the newer language version, run: go get [email protected]

# Write code that imports it, then:
go mod tidy    # adds what is imported, drops what is not, fixes go.sum
go build ./...

# Commit both files. They are the record of what your build used.
git add go.mod go.sum

Minimal version selection, worked through

Shows why two builds of the same commit, months apart, use the same dependency versions. It also explains a common surprise: after go get of one library, an unrelated dependency jumped a few versions, because that library required a higher one.

Example · text
Your module requires two libraries:

  module example.com/app
  require (
      example.com/a v1.0.0
      example.com/b v1.0.0
  )

Each of those requires the same third module, at different versions:

  module example.com/a      module example.com/b
  require example.com/c v1.2.0   require example.com/c v1.4.0

The newest release of c on the server is v1.9.0.

Selected version of c: v1.4.0
  - the highest version any module in the graph requires
  - v1.9.0 is ignored: nothing asks for it
  - v1.2.0 is not enough: b asked for more

To move to v1.9.0 you must say so:

  go get example.com/[email protected]   # adds a require line to your go.mod

What go.sum holds, and the error when it is short

go.sum is a verification file, not a list of what to use: versions come from go.mod. Deleting a line does not downgrade anything, it only removes the check. You meet this error whenever go.mod was edited by hand or go.sum was left out of a commit.

Example · text
go.sum has two lines per module version (hashes abbreviated here):

  github.com/google/uuid v1.6.0 h1:<hash of the module's files>
  github.com/google/uuid v1.6.0/go.mod h1:<hash of its go.mod>

A build whose go.sum is missing an entry stops:

  missing go.sum entry for module providing package github.com/google/uuid;
  to add: go mod download github.com/google/uuid

Fix it with:

  go mod tidy          # usual fix: recompute from what the code imports
  go mod verify        # check downloaded modules against go.sum

Discussion

  • Be the first to comment on this lesson.