# Go Packages

> 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).

- Skill: `muratmirgun/go-packages-2` (Agent Skill)
- Install (CLI): `npx skillmds@latest add muratmirgun/go-packages-2`
- Raw SKILL.md: https://api.skillmd.com/api/skills/muratmirgun/go-packages-2/raw
- Safety review: pending
- Works with: Claude Code, Claude.ai, OpenAI Codex
- Category: Coding & Dev Tools
- License: MIT
- Author: muratmirgun (https://skillmd.com/u/muratmirgun)
- Updated: 2026-09-17
- Page: https://skillmd.com/skills/muratmirgun/go-packages-2

---


# 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

1. **Package names describe what the package provides.** `util`, `helper`, `common`, `misc` are not names.
2. **Imports are grouped: stdlib first, then external.** `goimports` will keep this honest.
3. **Avoid `init()`** — and when unavoidable, keep it deterministic and I/O-free.
4. **`os.Exit` / `log.Fatal` only inside `main`.** Library code returns errors.
5. **Use the `run()` pattern** so `main` has a single exit point and deferred cleanup runs.
6. **CLI flags belong in `package main`.** Libraries take configuration as parameters.
7. **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](../../../skills/go-packages/references/package-layout.md) when deciding how to split a growing package, organizing `cmd/`, `internal/`, or designing a library API surface.

## Naming Packages

```go
// 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

```go
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](../../../skills/go-packages/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:

1. Deterministic — same result every run.
2. Independent of the order of other `init()`s.
3. Free of environment state (env vars, working dir, args).
4. 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`

```go
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`.

```go
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](../../../skills/go-packages/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

- [ ] Package name is concrete and unambiguous
- [ ] Imports are grouped (stdlib first), `goimports` clean
- [ ] No `init()` performs I/O or depends on env state
- [ ] `main` is a single `if err := run(); err != nil { log.Fatal(err) }`
- [ ] No `os.Exit` / `log.Fatal*` outside `main`
- [ ] Flags are defined only in `package main`
- [ ] No `import .` and no blank import outside `main`/tests
- [ ] Package's purpose fits in one sentence

## References

- [references/package-layout.md](../../../skills/go-packages/references/package-layout.md) — splitting packages, `cmd/`, `internal/`, public API surface
- [references/imports-and-main.md](../../../skills/go-packages/references/imports-and-main.md) — extended import grouping, the `run()` pattern, flag conventions
- [references/init-and-globals.md](../../../skills/go-packages/references/init-and-globals.md) — when `init` is acceptable, mutable globals, DI

