Writing Go Code

Purpose and Scope

This page explains the everyday workflow for organizing Go source: create a module, put related source files in packages, import the packages you use, write exported and unexported declarations deliberately, and keep generated or machine-maintained files recognizable. The goal is not to describe every language feature. Instead, it connects the public Go documentation model of modules and packages to concrete files in the Go repository, so you can recognize the same conventions in production code and in your own projects.

A Go package is the unit of compilation. Source files in one directory declare the same package name and are compiled together, so functions, types, constants, and variables from one file can be used by the other files in that package. A module is the release and dependency boundary that gives packages import paths. In typical project work you initialize a module, add packages as directories, write code under those packages, and use the go command to run, build, and test them.

Sources: src/cmd/internal/obj/s390x/condition_code.go, src/compress/flate/huffman_code.go, src/internal/types/errors/code_string.go

Relevant Source Files

  • src/cmd/internal/obj/s390x/condition_code.go — Shows a normal implementation file in package s390x with imports, a named type, constants, methods, comments, and a package-local API used by the assembler and compiler object machinery.
  • src/compress/flate/huffman_code.go — Shows a standard library implementation file in package flate with multiple imports, constants, compact helper types, constructors, package-level cached values, and implementation comments for a nontrivial algorithm.
  • src/internal/types/errors/code_string.go — Shows a generated Go source file with a clear generated-code header, package declaration, import, compile-time consistency checks, and string conversion support for internal compiler error codes.

Core Primitives

The first primitive is the package declaration. Each requested source file starts by declaring exactly one package, such as package s390x, package flate, or package errors. That line determines the namespace for every top-level declaration in the file. In your own code, package main is reserved for executable commands, while reusable code normally uses a descriptive library package name. The package name is not necessarily the full import path; it is the local name used by files that import the package.

The second primitive is the import block. A file imports only the packages it refers to directly. condition_code.go imports fmt to format an invalid mask string, while huffman_code.go imports math, math/bits, slices, and sync for numeric limits, bit operations, generic slice helpers, and once-only initialization. This style keeps dependencies visible at the top of each file and lets the go command and compiler reject unused imports, which is one reason Go source tends to stay mechanically tidy.

The third primitive is the top-level declaration. Go code is built from const, var, type, and func declarations. condition_code.go declares a named integer type, CCMask, then groups related constants such as Equal, Less, Greater, Always, Carry, and NoCarry. huffman_code.go declares constants, compact helper types such as hcode and literalNode, a struct type named huffmanEncoder, and functions that construct and populate encoders. These examples show the common pattern: define the domain vocabulary first, then attach behavior with functions and methods.

Sources: src/cmd/internal/obj/s390x/condition_code.go, src/compress/flate/huffman_code.go

Standard Project Workflow

Start a new project by choosing a module path and creating a module file. For local learning code, a path such as example.com/hello is enough. For code that may be published, choose a path that will remain stable, because package import paths are formed from the module path plus the package directory. A minimal workflow is to create a directory, run go mod init, add a .go file, then use go run for an executable or go test for a package with tests.

mkdir hello
cd hello
go mod init example.com/hello

Inside an executable command, use package main and define func main. Inside reusable code, use a package name that describes the directory’s purpose. The official tutorial pattern imports fmt for console output and imports another module path when calling code from a different module. That same import discipline appears in the repository examples: flate does not import the whole compression tree, only the packages it needs for the Huffman encoder implementation; s390x imports fmt only because its String method needs formatted fallback output.

package main
 
import (
    fmt
 
    example.com/greetings
)
 
func main() {
    message := greetings.Hello("Gladys")
    fmt.Println(message)
}

The next workflow step is to separate reusable packages from commands. A command is just a package main in a directory intended to build an executable. A library package exposes useful identifiers to callers. In Go, exported identifiers start with an upper-case letter, while unexported identifiers start with a lower-case letter. condition_code.go exposes CCMask and constants such as Equal and Always inside its package API surface, while helper details remain ordinary implementation code. huffman_code.go keeps several identifiers lower-case because they are implementation details of compress/flate, not public standard library API.

Sources: src/cmd/internal/obj/s390x/condition_code.go, src/compress/flate/huffman_code.go

System-to-Code Mapping

The source files demonstrate how a package becomes more than a directory of unrelated functions. In condition_code.go, the named type CCMask establishes a small domain model for four-bit condition code masks. Constants define the valid named values, methods such as Inverse, ReverseComparison, and String provide behavior, and comments explain platform-specific meaning. This is the idiomatic shape for a small abstraction: keep representation compact, name the operations that callers should use, and document assumptions close to the declaration that relies on them.

In huffman_code.go, the package organizes an algorithm through layered helpers. hcode stores a bit code and length in one integer-like value, methods extract the length and code, and newhcode centralizes construction. huffmanEncoder then owns the code table, bit counts, and reusable frequency cache needed by the compression algorithm. Package-level variables fixedLiteralEncoding and fixedOffsetEncoding use sync.OnceValue so the fixed encodings are generated lazily and shared safely. For your own projects, this illustrates how to turn an algorithm into cohesive types rather than one oversized function.

Generated source follows a different convention. code_string.go begins with a generated-by comment and a DO NOT EDIT warning. It still has normal Go syntax: package errors, an import of strconv, and declarations. The file includes compile-time array index checks that fail if numeric constants change without regenerating the string table. Generated files should be committed only when that is the project convention, should clearly identify the generator, and should avoid mixing hand edits with generated output.

Sources: src/internal/types/errors/code_string.go, src/compress/flate/huffman_code.go

Comments, Names, and Examples

Write comments where they explain meaning that is not obvious from the name alone. condition_code.go uses comments to explain IBM Z condition code bit numbering and why names such as Equal and Greater assume comparison semantics. huffman_code.go uses comments to describe the RFC-based Huffman encoder and the purpose of internal fields such as freqcache. These are not decorative comments; they preserve design constraints that a future maintainer needs before changing the code.

Use names that scale from local helpers to package APIs. Short names such as hcode can be appropriate for an unexported compact representation used throughout one implementation file. Longer names such as ReverseComparison or generateFixedLiteralEncoding are useful when the operation’s intent matters more than brevity. Exported names should be readable outside the package because they become part of the package’s contract. Unexported names should still be clear to maintainers, but they can assume local context.

Examples in Go documentation often live as executable-looking code snippets or tests, but the same principles apply in ordinary files. Keep the package declaration first, group imports, declare constants and types near the behavior that depends on them, and prefer small functions that make invariants visible. When a file grows to include several concepts, use comments and type boundaries to show how the pieces fit. The flate Huffman code is a useful model: constants establish limits, representation types define storage, and generator functions build reusable encoders.

Sources: src/cmd/internal/obj/s390x/condition_code.go, src/compress/flate/huffman_code.go

Practical Checklist

When writing new Go code, first decide whether the directory is a command or a library package. Then initialize or join the appropriate module, choose package names that match the directory’s responsibility, and import only what each file uses. Add exported declarations only when callers outside the package need them. Keep implementation helpers unexported, attach methods to named types when behavior belongs with a representation, and add comments for exported API or subtle invariants.

Before sharing the code, run the normal go command workflow: format the source, run tests, and build the command or package. If you introduce generated files, include a clear generated-code header and document the regeneration command near the source convention used by the project. For deeper next steps, read the module tutorials for dependency tracking, the testing tutorial for package tests, and Effective Go for naming, comments, and formatting conventions.