Go organizes source into packages and modules, reports recoverable failures as values, and provides reflection-based text and HTML templates in its standard library. The same design principle connects these areas: make dependencies and control flow explicit at the API boundary.
Modules, Packages, and Builds #
A package is the compilation and namespace boundary formed by non-test .go
files with the same package declaration in one directory. A module is a
versioned collection of packages rooted at go.mod. The module path supplies
the import-path prefix:
mkdir example
cd example
go mod init example.com/team/example
go get golang.org/x/sync@v0.17.0
go mod tidy
go test ./...
go build ./cmd/servergo get changes module requirements; go install package@version installs a
command without changing the current module. In current Go releases, module
mode is the standard dependency model, and a module can live outside GOPATH.
Nested modules are separate dependency boundaries and are excluded from the
parent module’s package pattern.
The build cache stores compiled packages and test results under GOCACHE.
Downloaded module source is stored under GOMODCACHE, commonly
$GOPATH/pkg/mod. Inspect actual locations with:
go env GOCACHE GOMODCACHE GOPATHDo not point GOCACHE and GOMODCACHE at the same directory because they have
different layouts and lifecycle rules. CI can cache both, keyed by the Go
version, target platform, and dependency files, while still treating
go.sum as the authenticated module-content record.
A package main containing func main() builds an executable and is not an
importable library boundary. Put reusable behavior in another package; keep
flag parsing and process exit decisions in main.
Errors, Panic, and Cleanup #
The built-in error interface contains Error() string. Functions return an
error when a caller can make a local decision:
f, err := os.Open(path)
if err != nil {
return fmt.Errorf("open %q: %w", path, err)
}
defer f.Close()Wrap with %w when callers should retain access to the cause through
errors.Is or errors.As. A panic indicates an unrecoverable invariant failure
inside the current abstraction. Libraries should not use panic as a substitute
for ordinary error results. A boundary such as an HTTP server may recover a
panic to isolate one request, log a stack, and convert the failure into an
error response; recovery does not make partially mutated state valid.
defer evaluates the function value and arguments immediately, then executes
deferred calls in last-in, first-out order when the surrounding function
returns or panics. A deferred closure can inspect named return values. Cleanup
errors still need explicit handling, especially for writable files:
func writeFile(path string, data []byte) (err error) {
f, err := os.Create(path)
if err != nil {
return err
}
defer func() {
if closeErr := f.Close(); err == nil {
err = closeErr
}
}()
_, err = f.Write(data)
return err
}Close can report delayed write or metadata errors. Read-only close errors are
less likely to affect the operation’s result, but applications can still record
them where the resource contract requires it.
Declarations, Shadowing, and Comma-Ok Forms #
:= declares at least one new variable in the current lexical block. A nested
declaration can shadow an outer error:
var err error
for {
var message []byte
message, err = readMessage()
if err != nil {
break
}
_ = message
}
return errUsing message, err := readMessage() inside the loop would declare a new
err; the outer value would remain unchanged. Keep declarations close to use
but use assignment when a result must escape the block.
Three language operations have comma-ok forms:
value, present := m[key] // map lookup
value, open := <-ch // channel receive
value, matches := x.(Target) // type assertionWithout the boolean, a failed type assertion panics, while a missing map entry and a receive from a closed channel produce zero values. The boolean therefore separates a real zero value from absence or closure.
Within a constant declaration, iota is the zero-based constant index:
type Permission uint8
const (
Read Permission = 1 << iota
Write
Execute
)Insertions can change later values, so persist or transmit an iota-based
enumeration only when every value is assigned deliberately or compatibility is
otherwise controlled.
Defined Types, Aliases, and Reference-Like Values #
An alias introduces another spelling for the same type; a definition creates a distinct named type with the specified underlying type:
type Bytes = []byte
type UserID stringBytes and []byte are identical. UserID and string require conversion
when neither assignment rule applies, and methods can be declared on UserID.
Slices, maps, channels, functions, and interfaces contain references to runtime state and are inexpensive descriptors to copy. Passing a slice by value copies its pointer, length, and capacity, not its elements. Copying a map or channel value likewise does not duplicate its backing state. A pointer to one of these values is needed only when the function must replace the caller’s descriptor or represent an additional state.
Go has no class-subtyping relation. A concrete type satisfies an interface by
having its methods, and embedding promotes selected fields and methods without
turning one struct into a subtype of another. Method sets differ between T
and *T: methods with a value receiver belong to both method sets, while a
pointer-receiver method belongs to *T.
Template Parsing and Execution #
text/template generates arbitrary text. html/template implements the same
core language but contextually escapes untrusted values for HTML, attributes,
URLs, JavaScript, and CSS. Use html/template for HTML output; converting
untrusted text to a trusted content type such as template.HTML bypasses the
protection.
Parse templates, register functions before parsing expressions that name them, and check execution errors:
const source = `{{define "row"}}<li>{{.Name}}</li>{{end}}
<ul>{{range .Items}}{{template "row" .}}{{else}}<li>empty</li>{{end}}</ul>`
tmpl, err := template.New("page").Option("missingkey=error").Parse(source)
if err != nil {
return err
}
if err := tmpl.Execute(output, data); err != nil {
return err
}An action appears between {{ and }}. Dot (.) is the current argument.
Pipelines pass one command’s final value to the next command. if, with, and
range execute conditionally; range changes dot for each element.
define, template, and block create and invoke named templates. Variables
such as {{$root := .}} preserve a value when dot changes.
A template invocation accepts one pipeline argument:
{{template "card" .}}Pass several logical values by wrapping them in a struct, map, slice, or helper result. Prefer a typed view-model struct because field names and ownership stay visible in Go:
type CardData struct {
User User
Enabled bool
}Template.Execute may already have written partial output when an evaluation
fails. Render into a bytes.Buffer first when the caller requires all-or-nothing
output. After parsing, a template can execute concurrently provided callers do
not mutate it and writers are independent.
Editor and Tooling Boundaries #
The conventional filename suffix is .tmpl, .gotmpl, or a content suffix
such as .html. Editor support depends on the extension and embedded-language
grammar; no filename changes Go’s template semantics. Format and test the Go
code with gofmt, go vet, and go test, then execute representative template
data in tests because the Go compiler cannot type-check reflected field and
method names inside template source.
The authoritative references are the Go language
specification, the go
command, and the package documentation for
text/template and
html/template.
Comments
Read or comment on GitHub (sign-in required to comment)