DEV Community

Shrijith Venkatramana
Shrijith Venkatramana

Posted on

A Deep Dive Into How Go Modules Work

Hi, I'm Shrijith Venkatramana. Right now, I'm building on an aggregation of 50,000+ resources on my site Free DevTools. This site hosts many free developer tools, manuals, cheatsheets and icon sets - which you can download or use without any login. Do give it a try here

Go modules are the standard way to manage dependencies in Go projects since Go 1.11. They help organize code, handle versions, and make builds reproducible. This article breaks down how modules function, with practical examples to get you up to speed.

Understanding the Basics of Go Modules

Go modules provide a system for versioning and dependency management. Before modules, Go used GOPATH, which often led to version conflicts. Modules solve this by defining a module as a collection of packages with a unique path and version.

A module is identified by its module path, like github.com/user/project. Each module has a go.mod file that lists dependencies and their versions.

To enable modules, set the GO111MODULE environment variable to auto or on, but in recent Go versions, it's on by default outside GOPATH.

Here's a simple example to create a module:

// main.go
package main

import "fmt"

func main() {
    fmt.Println("Hello, Modules!")
}
Enter fullscreen mode Exit fullscreen mode

Run go mod init example.com/mymodule in the directory with main.go. This creates a go.mod file:

module example.com/mymodule

go 1.21
Enter fullscreen mode Exit fullscreen mode

Build and run with go build and ./mymodule (or mymodule.exe on Windows). Output: Hello, Modules!

For more on module basics, check the official Go documentation: Modules Overview.

Creating Your First Go Module Step by Step

Start with an empty directory. Create a file greet.go:

// greet.go
package greet

import "fmt"

func Greet(name string) {
    fmt.Printf("Hello, %s!\n", name)
}
Enter fullscreen mode Exit fullscreen mode

Then main.go:

// main.go
package main

import "example.com/mymodule/greet"

func main() {
    greet.Greet("Developer")
}
Enter fullscreen mode Exit fullscreen mode

Initialize the module: go mod init example.com/mymodule.

The go.mod now exists. To tidy dependencies (though none yet), run go mod tidy.

Build: go build. Run: ./mymodule. Output: Hello, Developer!

This setup shows how packages within a module import each other using the module path.

Inside the go.mod File: Key Elements Explained

The go.mod file is the heart of a module. It declares the module path, Go version, dependencies, and more.

Key directives:

  • module: Defines the module path.
  • go: Specifies the minimum Go version.
  • require: Lists direct dependencies with versions.
  • replace: Overrides a dependency's path or version.
  • exclude: Skips specific versions.
  • retract: Marks versions as retracted.

Example go.mod after adding a dependency:

module example.com/mymodule

go 1.21

require github.com/google/uuid v1.3.0
Enter fullscreen mode Exit fullscreen mode

Run go mod edit -require=github.com/google/uuid@v1.3.0 to add it manually, or use go get.

To understand directives better, see go.mod Reference.

Adding and Updating Dependencies Effectively

Use go get to add dependencies. It updates go.mod and downloads modules.

Example: Add github.com/google/uuid.

In main.go:

// main.go
package main

import (
    "fmt"
    "github.com/google/uuid"
)

func main() {
    id := uuid.New()
    fmt.Println(id)
}
Enter fullscreen mode Exit fullscreen mode

Run go get github.com/google/uuid. This adds to go.mod and creates go.sum for checksums.

To update: go get github.com/google/uuid@latest.

go.sum ensures integrity; it lists hashes for modules.

For a table of common commands:

Command Purpose
go get pkg Add or update dependency
go mod tidy Remove unused, add missing deps
go mod why pkg Explain why a package is needed
go list -m all List all modules and versions

How Versioning and Semantic Versioning Fit In

Go uses semantic versioning (SemVer) like v1.2.3. Modules support major versions via suffixes: v2+ in the path, like example.com/mymodule/v2.

When requiring, specify versions: require example.com/other v1.0.0.

Go resolves versions using minimal version selection (MVS): Picks the highest version that satisfies all requirements.

Example: If one dep requires v1.2.0 and another v1.3.0, it chooses v1.3.0 if compatible.

To tag a version: git tag v1.0.0 and push.

Pseudo-versions like v0.0.0-20230929123456-abcdef123456 are used for untagged commits.

For details on versioning: Module Versions.

Working with Multiple Modules in Workspaces

Workspaces allow editing multiple modules together, useful for monorepos.

Create go.work with go work init.

Add modules: go work use ./mod1 ./mod2.

Example structure:

  • workspace/
    • mod1/
    • go.mod
    • main.go
    • mod2/
    • go.mod
    • lib.go
    • go.work

go.work:

go 1.21

use (
    ./mod1
    ./mod2
)
Enter fullscreen mode Exit fullscreen mode

In mod1's main.go, import mod2 using its path.

This overrides go.mod replaces for local development.

Troubleshooting Common Module Issues

Common problems include proxy issues, checksum mismatches, or invalid paths.

For checksum errors: Run go mod verify.

If behind a proxy, set GOPROXY=https://proxy.golang.org,direct.

Invalid module path: Ensure it starts with a domain like example.com.

Example fix for a bad import:

If import fails, check go.mod for correct require.

Table of errors:

Error Type Fix
Checksum mismatch go mod download or delete cache
Module not found Check network or GOPROXY
Version conflict Use go mod graph to inspect

For proxy setup: GOPROXY Environment.

Advanced Techniques for Module Management

Use replace in go.mod for local forks: replace github.com/other/mod => ../local/mod.

Vendoring: go mod vendor copies deps to vendor/ for offline builds.

Minimal modules: Keep go.mod clean with go mod tidy.

For private modules, set GOPRIVATE=example.com/private.

Example with replace:

Add to go.mod: replace github.com/google/uuid => ./local/uuid.

This points to a local directory.

Go modules evolve with each release, so check release notes for updates. Mastering them ensures reliable, scalable projects. Experiment with these examples in your setup to solidify your understanding.

Top comments (0)