Multi-Module Workspaces
Purpose and Scope
A multi-module workspace is a development arrangement where one checkout contains more than one Go module and the Go command is told to treat those modules as active local work at the same time. This solves a common problem: you want to change a library module and an application module together, then run the application against your local edits without publishing a version or adding temporary replace directives to each module. The official workflow introduced in Go 1.18 uses a go.work file at the workspace root to list the module directories that participate in that shared build view.
The Go repository positions this workflow as part of the standard Go distribution rather than as an add-on. The root README identifies Go as an open source language for building simple, reliable, and efficient software, points readers to official binary distributions, and distinguishes the canonical source repository from the GitHub mirror. That matters for workspaces because the feature is exercised through the installed Go command that ships with the distribution, and the same source tree contains the compiler and toolchain commands used when a workspace package is built or run. Sources: README.md
Use a workspace when separate modules need to evolve together. A module remains the unit that owns a module path, its go.mod file, and its dependency requirements. A workspace is the temporary developer-level view above those modules. The go.work file says, in effect, which local module roots should take precedence while commands such as go run, go test, and go build resolve imports. This lets you keep publishable module metadata stable while still getting fast local feedback across repository boundaries.
The conceptual boundary is important. A workspace does not merge modules, rename their module paths, or make every directory under the workspace root a package. Each participating module still has its own go.mod file, package layout, imports, and tests. The workspace file is an instruction to the Go command about local development context. When the command compiles code from that context, it still enters the same bundled toolchain pipeline represented in this repository by command packages such as the compiler, assembler, cgo processor, and diagnostic tools. Sources: src/cmd/compile/internal/gc/main.go, src/cmd/asm/main.go, src/cmd/cgo/main.go, src/cmd/addr2line/main.go
Relevant Source Files
- README.md — Establishes the Go project, its canonical source repository, binary distribution entry points, source installation entry points, license context, and contribution links used to orient a reader before relying on workspace-capable Go tooling.
- misc/go_android_exec/main.go — Shows how the Go tool can execute built test binaries through a platform-specific wrapper; useful context for understanding that go test and related commands may delegate execution to environment-specific helpers after compiling packages.
- src/cmd/addr2line/main.go — Provides a minimal go tool addr2line implementation used by pprof, illustrating that the distribution includes auxiliary tools invoked through the Go toolchain rather than only the compiler.
- src/cmd/asm/main.go — Implements the assembler command entry point, parses assembler flags, selects architecture support, and writes object files, grounding the lower-level build tools used after package loading resolves workspace modules.
- src/cmd/cgo/main.go — Defines cgo's main package data structures for collecting package, file, C reference, and generated-output information, showing how special package compilation paths still fit inside the toolchain.
- src/cmd/compile/internal/gc/main.go — Documents the compiler Main entry point that parses flags and source files, type-checks a package, compiles functions to machine code, and writes compiled package definitions.
Core Primitives
The first primitive is the module. A module is the versioned unit described by a go.mod file and named by a module path such as example.com/hello. In the official workspace tutorial, the initial application module imports golang.org/x/example/hello/reverse and can run before any workspace exists. That step demonstrates that workspaces build on normal module-aware development: you still initialize modules with go mod init, add requirements with go get, and write ordinary package imports in Go source.
The second primitive is the workspace root. This is the directory where you create the go.work file. It is usually the parent directory that contains the modules you want to develop together. The workspace root is not itself necessarily a Go module. Its job is to hold the go.work file and provide a place from which the Go command can discover the set of active local modules. Keeping this separate helps preserve the identity of each module while allowing a convenient shared editing and command-running location.
The third primitive is the use list in go.work. In the tutorial flow, go work init is used to create a workspace file that points at one or more module directories, and go work use adds more module directories later. Once a module appears in the workspace, imports matching that module path resolve to the local directory instead of to the downloaded module cache. That local preference is the main feature: it lets edits in one module immediately affect commands run in another module in the same workspace.
The fourth primitive is the installed toolchain. The repository snippets show that Go is not a single monolithic binary internally; the source tree contains command entry points for tools used by builds, tests, profiling, cgo, assembly, and compilation. The compiler main function describes parsing command-line flags and Go source files, type-checking, compiling functions to machine code, and writing package output. The assembler creates object files for a selected architecture, and cgo gathers information from Go files that import C. Sources: src/cmd/compile/internal/gc/main.go, src/cmd/asm/main.go, src/cmd/cgo/main.go
Workspace Tutorial Flow
Start by creating a directory that will hold the workspace and the modules you want to edit together. The official tutorial creates a workspace directory, then creates a hello module inside it. That first module can be initialized and run exactly like any other module. The point is to establish a baseline: before introducing a go.work file, confirm that the application module builds and runs with its dependency resolved through the normal module system.
mkdir workspace
cd workspace
mkdir hello
cd hello
go mod init example.com/hello
go get golang.org/x/example/hello/reverse
go run .A minimal hello.go for that first module imports fmt and the reverse package, then prints the reversed string. This is ordinary Go code in package main. Workspaces do not change the language syntax, import declarations, or the requirement that an executable command use package main. They change how the Go command chooses between downloaded module versions and local module directories while resolving those imports.
package main
import (
"fmt"
"golang.org/x/example/hello/reverse"
)
func main() {
fmt.Println(reverse.String("Hello"))
}Next, create the workspace file from the parent directory. A typical first step is go work init ./hello, which records the hello module as part of the workspace. After that, you can add another module, such as a local checkout of golang.org/x/example, with go work use. The important result is not the command spelling alone; it is the change in resolution behavior. The application can now import a package whose module path is satisfied by your local sibling checkout.
cd ..
go work init ./hello
# after adding or checking out another module:
go work use ./example
go run ./helloWhen you change code in the local dependency module, rerun the application module from the workspace. The Go command sees both modules in the workspace file and uses the local dependency source. This is the feedback loop the workspace feature is designed to shorten. You can edit an API, update its caller, run the caller, and add tests before publishing a new module version. If the API change would break existing users, keep the compatibility lesson from the tutorials in mind: prefer adding a new exported function when you need to preserve the old signature.
System-to-Code Mapping
At the user level, the workspace feature belongs to the Go command workflow: initialize modules, create a go.work file, list participating module directories, and run commands from the workspace. At the repository level, the supplied source evidence maps the lower layers that make those commands real. Once module loading has selected the package sources to build, the compiler entry point takes source files, performs front-end work such as type checking, lowers and compiles functions, and writes package output. Sources: src/cmd/compile/internal/gc/main.go
Assembler and cgo behavior matter for workspace users because a workspace can contain any normal Go package, including packages with assembly files or import "C". The assembler entry point sets build configuration, parses flags, selects the target architecture, parses assembly input, and writes object output. The cgo main file defines package and file state used to collect C references, generated files, preambles, exported functions, directives, and compiler/linker options. Workspace mode does not make these packages special; it simply ensures the local module versions are the ones reaching the same build machinery. Sources: src/cmd/asm/main.go, src/cmd/cgo/main.go
Testing and execution may also involve environment-specific helpers. The Android execution wrapper is designed to be used by the Go tool as go_android_GOARCH_exec and runs binaries on an Android device through adb. It serializes adb commands, waits for a booted device, copies the Go root once per build script run, prepares temporary directories, and returns the test binary's exit code. This illustrates a practical point for workspaces: after the Go command resolves local modules and builds binaries, the execution environment can still be specialized by GOOS, GOARCH, and test execution wrappers. Sources: misc/go_android_exec/main.go
Diagnostic tools are part of the same distribution story. The addr2line command is a minimal simulation of GNU addr2line intended for pprof, reads program counters from standard input, and prints function and file-line information from a binary. A workspace does not change profiling or symbolization concepts, but it can make local source paths more relevant during active development because the binary was built from local modules in the workspace rather than only from downloaded module cache contents. Sources: src/cmd/addr2line/main.go
Practical Guidance and Next Steps
Keep go.work files focused on local development. If a repository intentionally contains multiple modules that should usually be edited together, committing a go.work file can be useful for contributors. If the workspace only reflects your machine layout, leave it local. Either way, keep each module's go.mod accurate, because published users and module proxies consume module metadata, not your private workspace view. Before releasing a module, verify commands outside the workspace or with the intended module requirements so you do not accidentally rely on unpublished sibling edits.
Use the workflow in small steps: create or clone the modules, verify each module builds on its own, initialize the workspace, add the module directories, then run the application and tests that cross module boundaries. If a command behaves unexpectedly, inspect the workspace file first, then inspect each module's go.mod. After that, treat failures like ordinary Go failures: import path errors belong to module/package layout, compile errors belong to source and type checking, cgo or assembly errors belong to those package features, and runtime or profiling questions may involve the distributed tools shown in this repository.
Related pages: Modules Overview, go.mod Reference, Managing Dependencies, The go Command, Build and Install, Testing.