Where Go looks for your code
This is only about code you write yourself, not downloading other people’s modules. I came from Python and JavaScript and the import rules took me a while, so these are my notes. Everything below I ran with Go 1.26.1.
the model
A module is a folder with a go.mod. A package is one directory. An import path is the module path plus the directory path inside the module. There are no relative imports.
The directory name should match the package name. Go does not force it, but it will confuse you when they differ. A name starting with a capital letter is exported, anything else stays inside the package.
one module, several packages
app/
go.mod
main.go
helper.go
greet/greet.go
go mod init example.com/app
// greet/greet.go
package greet
func Hello(name string) string { return "hello " + name }
// main.go
package main
import (
"fmt"
"example.com/app/greet"
)
func main() { fmt.Println(greet.Hello("omar"), helper()) }
helper() lives in helper.go, same package main. Files in one package see each other without any import.
$ go run .
hello omar !
$ go run main.go
# command-line-arguments
./main.go:9:48: undefined: helper
go run main.go compiles only the files you name. Use go run . so it takes the whole package.
internal
A package under a directory called internal can only be imported by code inside the parent of that internal directory. Inside app, anything can import example.com/app/internal/secret. Another module cannot.
main.go:3:8: use of internal package example.com/lib/internal not allowed
That is the error from app2 importing example.com/lib/internal. If the other module is not wired up at all, you get the “no required module” error instead, because Go cannot even find it.
import cycles
If a imports b and b imports a, it does not build.
package example.com/app
imports example.com/app/a from main.go
imports example.com/app/b from a.go
imports example.com/app/a from b.go: import cycle not allowed
I read this from the bottom up. The fix is almost always to move whatever both sides need into a third package, say types, and have a and b both import that. Passing a function or an interface in instead also works.
two local modules
Say app2 uses example.com/lib, and neither is published. Without anything extra:
main.go:6:2: no required module provides package example.com/lib; to add it:
go get example.com/lib
There are two ways to fix it. The first is a workspace, for development:
go work init ./app2 ./lib
This writes a go.work that lists both folders, and go run . in app2 then finds lib. I do not commit go.work, it has my local layout in it.
The second is a replace, which does get committed:
cd app2
go mod edit -replace example.com/lib=../lib
go mod tidy
tidy adds the require line with a fake version, so go.mod ends up like this:
replace example.com/lib => ../lib
require example.com/lib v0.0.0-00010101000000-000000000000
Replace only counts in the main module, the one you run go from. If something else depends on app2, it has to repeat the replace itself.
A package main cannot be imported, even with a replace:
main.go:3:8: import "example.com/tool" is a program, not an importable package
Put the shared code in a normal package.
errors you will hit
no required module provides package github.com/foo/bar; to add it:
The module is not in your go.mod. For a real remote one, go get it. For a local one, use go.work or replace as above. If you mistyped the path, fix that.
package strngs is not in std (/usr/local/go/src/strngs)
The import has no dot in the first element, so Go looks in the standard library. Usually a typo, or your module path is missing from the import.
./main.go:9:48: undefined: helper
You ran go run main.go on a package split over files. Use go run ..
use of internal package example.com/lib/internal not allowed
You are outside the parent of internal. Move the package out of internal, or put the importing code in the same module.
import cycle not allowed
See above, pull the shared part into a third package.
gopath
GOPATH mode is legacy and only comes back with GO111MODULE=off, where the same import fails with cannot find package ... in any of. Today $GOPATH is just the module cache (~/go/pkg/mod) and where go install puts binaries (~/go/bin).