Go Packages and Imports
A package is a unit of meaning, not a folder of files. Name it for what it provides, keep imports tidy, and put startup logic where it belongs.
Core Rules
- Package names describe what the package provides.
util, helper, common, misc are not names.
- Imports are grouped: stdlib first, then external.
goimports will keep this honest.
- Avoid
init() — and when unavoidable, keep it deterministic and I/O-free.
os.Exit / log.Fatal only inside main. Library code returns errors.
- Use the
run() pattern so main has a single exit point and deferred cleanup runs.
- CLI flags belong in
package main. Libraries take configuration as parameters.
- Blank imports belong in
main or tests. Dot imports are essentially never appropriate.
Decision: How to Split a Package
| Question |
If "yes" |
| Can you state the package's purpose in one sentence? |
Probably right-sized |
| Do its files never share unexported symbols? |
Likely two packages glued by directory |
| Do distinct caller groups touch distinct files? |
Split along caller boundaries |
| Is the godoc index so long callers cannot find things? |
Split for discoverability |
| Does splitting create import cycles? |
Don't split |
Read references/package-layout.md when deciding how to split a growing package, organizing cmd/, internal/, or designing a library API surface.
Naming Packages
// Good — meaningful
db := spannertest.NewDatabaseFromFile(...)
_, err := f.Seek(0, io.SeekStart)
// Bad — vague
db := test.NewDatabaseFromFile(...)
_, err := f.Seek(0, common.SeekStart)
Generic words may appear as part of a name (stringutil, iotest) but not as the whole name. Match the package to a concept the caller already knows.
Imports
import (
"fmt"
"os"
"github.com/foo/bar"
"rsc.io/goversion/version"
)
| Rule |
Guidance |
| Group order |
stdlib, then external; extended order may also separate protos and side-effect imports |
| Renaming |
Avoid unless there is a collision; rename the more-local import |
Blank import (import _) |
Only main and tests |
Dot import (import .) |
Effectively never; rare in test files for circular deps |
Read references/imports-and-main.md for extended import grouping, proto pb suffixes, the run() pattern, and CLI flag conventions.
Avoid init()
When you must use init(), make it:
- Deterministic — same result every run.
- Independent of the order of other
init()s.
- Free of environment state (env vars, working dir, args).
- Free of I/O (filesystem, network, syscalls).
Acceptable uses:
- Precomputing a constant that cannot fit in a single expression.
- Registering pluggable hooks (
database/sql drivers).
If your init reads a file or calls a network API, refactor it into an explicit Setup() the caller invokes.
Exit Only in main
func main() {
if err := run(); err != nil {
log.Fatal(err)
}
}
func run() error {
// all the real work
return nil
}
Why:
log.Fatal and os.Exit skip defer. Anywhere except main, that means leaked files, half-flushed buffers, undeleted temp dirs.
- The
run() pattern gives you one place to log a clean error and one place to set the exit code.
CLI Flags
- Define flags in
package main.
- Flag names use
snake_case: --output_dir, not --outputDir.
- Libraries accept configuration through function parameters, never reach for
flag.Lookup.
func main() {
outputDir := flag.String("output_dir", ".", "directory for output files")
flag.Parse()
if err := mylib.Generate(*outputDir); err != nil {
log.Fatal(err)
}
}
Read references/init-and-globals.md for the boundaries between safe init-time computation, mutable globals, and dependency injection.
Anti-Patterns
| Anti-pattern |
Why it hurts |
Do this instead |
package util |
Meaningless name; import conflicts |
Name after the concept |
| One huge package with 50 files |
Hard to navigate, slow builds |
Split by responsibility |
init() reads config from disk |
Side effect at import time |
Explicit Setup() in main |
log.Fatal in library code |
Skips defers, untestable |
Return an error |
os.Exit in a request handler |
Same — plus crashes the server |
Return an error to the framework |
import _ "pkg" in a library |
Side effects on every importer |
Register explicitly |
import . "pkg" to "save typing" |
Tools lose track of where names come from |
Use the package qualifier |
| Library reads a flag at import time |
Untestable, non-reusable |
Accept config as parameter |
Verification Checklist
References
1---2name: go-packages-23description: Use when creating Go packages, organizing imports, managing dependencies, or structuring a Go project. Covers meaningful package names, package size, import grouping (stdlib first, then external), blank/dot imports, the run() pattern in main, init() restrictions, and CLI flag conventions. Apply proactively when starting a new module or splitting a growing codebase, even if the user did not explicitly ask about package layout. Does not cover identifier naming inside packages (see go-naming).4license: MIT5---67# Go Packages and Imports89A package is a unit of meaning, not a folder of files. Name it for what it provides, keep imports tidy, and put startup logic where it belongs.1011## Core Rules12131. **Package names describe what the package provides.** `util`, `helper`, `common`, `misc` are not names.142. **Imports are grouped: stdlib first, then external.** `goimports` will keep this honest.153. **Avoid `init()`** — and when unavoidable, keep it deterministic and I/O-free.164. **`os.Exit` / `log.Fatal` only inside `main`.** Library code returns errors.175. **Use the `run()` pattern** so `main` has a single exit point and deferred cleanup runs.186. **CLI flags belong in `package main`.** Libraries take configuration as parameters.197. **Blank imports** belong in `main` or tests. **Dot imports** are essentially never appropriate.2021## Decision: How to Split a Package2223| Question | If "yes" |24|---|---|25| Can you state the package's purpose in one sentence? | Probably right-sized |26| Do its files never share unexported symbols? | Likely two packages glued by directory |27| Do distinct caller groups touch distinct files? | Split along caller boundaries |28| Is the godoc index so long callers cannot find things? | Split for discoverability |29| Does splitting create import cycles? | Don't split |3031> Read [references/package-layout.md](../../../skills/go-packages/references/package-layout.md) when deciding how to split a growing package, organizing `cmd/`, `internal/`, or designing a library API surface.3233## Naming Packages3435```go36// Good — meaningful37db := spannertest.NewDatabaseFromFile(...)38_, err := f.Seek(0, io.SeekStart)3940// Bad — vague41db := test.NewDatabaseFromFile(...)42_, err := f.Seek(0, common.SeekStart)43```4445Generic words may appear as part of a name (`stringutil`, `iotest`) but not as the whole name. Match the package to a concept the caller already knows.4647## Imports4849```go50import (51 "fmt"52 "os"5354 "github.com/foo/bar"55 "rsc.io/goversion/version"56)57```5859| Rule | Guidance |60|---|---|61| Group order | stdlib, then external; extended order may also separate protos and side-effect imports |62| Renaming | Avoid unless there is a collision; rename the more-local import |63| Blank import (`import _`) | Only `main` and tests |64| Dot import (`import .`) | Effectively never; rare in test files for circular deps |6566> Read [references/imports-and-main.md](../../../skills/go-packages/references/imports-and-main.md) for extended import grouping, proto `pb` suffixes, the `run()` pattern, and CLI flag conventions.6768## Avoid `init()`6970When you must use `init()`, make it:71721. Deterministic — same result every run.732. Independent of the order of other `init()`s.743. Free of environment state (env vars, working dir, args).754. Free of I/O (filesystem, network, syscalls).7677Acceptable uses:7879- Precomputing a constant that cannot fit in a single expression.80- Registering pluggable hooks (`database/sql` drivers).8182If your `init` reads a file or calls a network API, refactor it into an explicit `Setup()` the caller invokes.8384## Exit Only in `main`8586```go87func main() {88 if err := run(); err != nil {89 log.Fatal(err)90 }91}9293func run() error {94 // all the real work95 return nil96}97```9899Why:100101- `log.Fatal` and `os.Exit` skip `defer`. Anywhere except `main`, that means leaked files, half-flushed buffers, undeleted temp dirs.102- The `run()` pattern gives you one place to log a clean error and one place to set the exit code.103104## CLI Flags105106- Define flags in `package main`.107- Flag names use `snake_case`: `--output_dir`, not `--outputDir`.108- Libraries accept configuration through function parameters, never reach for `flag.Lookup`.109110```go111func main() {112 outputDir := flag.String("output_dir", ".", "directory for output files")113 flag.Parse()114 if err := mylib.Generate(*outputDir); err != nil {115 log.Fatal(err)116 }117}118```119120> Read [references/init-and-globals.md](../../../skills/go-packages/references/init-and-globals.md) for the boundaries between safe init-time computation, mutable globals, and dependency injection.121122## Anti-Patterns123124| Anti-pattern | Why it hurts | Do this instead |125|---|---|---|126| `package util` | Meaningless name; import conflicts | Name after the concept |127| One huge package with 50 files | Hard to navigate, slow builds | Split by responsibility |128| `init()` reads config from disk | Side effect at import time | Explicit `Setup()` in `main` |129| `log.Fatal` in library code | Skips defers, untestable | Return an error |130| `os.Exit` in a request handler | Same — plus crashes the server | Return an error to the framework |131| `import _ "pkg"` in a library | Side effects on every importer | Register explicitly |132| `import . "pkg"` to "save typing" | Tools lose track of where names come from | Use the package qualifier |133| Library reads a flag at import time | Untestable, non-reusable | Accept config as parameter |134135## Verification Checklist136137- [ ] Package name is concrete and unambiguous138- [ ] Imports are grouped (stdlib first), `goimports` clean139- [ ] No `init()` performs I/O or depends on env state140- [ ] `main` is a single `if err := run(); err != nil { log.Fatal(err) }`141- [ ] No `os.Exit` / `log.Fatal*` outside `main`142- [ ] Flags are defined only in `package main`143- [ ] No `import .` and no blank import outside `main`/tests144- [ ] Package's purpose fits in one sentence145146## References147148- [references/package-layout.md](../../../skills/go-packages/references/package-layout.md) — splitting packages, `cmd/`, `internal/`, public API surface149- [references/imports-and-main.md](../../../skills/go-packages/references/imports-and-main.md) — extended import grouping, the `run()` pattern, flag conventions150- [references/init-and-globals.md](../../../skills/go-packages/references/init-and-globals.md) — when `init` is acceptable, mutable globals, DI