English | 繁體中文
Go / Golang
Go is a compiled, statically typed language with garbage collection, explicit error values, structural interfaces, lightweight goroutines, and channels. Prefer simple, readable code; run gofmt and let go vet/tests guide refactoring.
Toolchain and modules
go mod init example.com/myapp # create go.mod
go get example.com/lib@v1.2.3 # add or change a dependency
go mod tidy # synchronize go.mod/go.sum
go run . # compile and run
go build ./... # build all packages
go test ./... -race -cover # tests, race detector, coverage
go vet ./... # suspicious constructs
gofmt -w . # canonical formatting
go doc fmt.Println # package/API documentation
go env GOPATH GOMOD GOVERSIONA directory is normally one package. A module can contain many packages. internal/ restricts imports to the parent tree; cmd/ commonly contains executables. Keep package names short and lowercase. Exported identifiers start with an uppercase letter and should have doc comments.
Syntax and core types
package main
import "fmt"
const (
MaxRetries = 3
_ = iota // iota is an untyped integer constant generator
)
var count int = 1
name := "Go" // short declaration; only inside functions
// Zero values: 0, false, "", nil pointer/slice/map/function/interface/channel.
var (
n int
ok bool
text string
nums []int // nil slice is safe to range over and append to
table map[string]int
)
// Built-ins: bool; signed/unsigned integers; float32/64; complex64/128;
// string (immutable bytes, usually UTF-8); byte = uint8; rune = int32.
// Conversions are explicit: int64(n), float64(n), []byte(text), string(data).
var (
a [3]int // array: length is part of its type
s = []int{1, 2, 3} // slice: pointer, length, capacity
m = map[string]int{} // map; lookup returns (value, found)
)
s = append(s, 4)
copy(s[1:], s[:2])
for key, value := range m { _ = key; _ = value }
// Structs group named fields; embedding promotes fields/methods.
type User struct {
ID int `json:"id"` // struct tag, read by reflection/packages
Name string `json:"name"`
}
u := User{ID: 1, Name: "Ada"}
ptr := &u
ptr.Name = "Grace" // automatic dereference for selectors
// Pointer: *T; address: &x; dereference: *p. There is no pointer arithmetic.Control flow
if value, found := m["go"]; found {
fmt.Println(value)
} else {
fmt.Println("missing")
}
for i := 0; i < 3; i++ {} // the only loop keyword
for condition() {} // while-style
for range s {} // iterate values/indexes
switch name {
case "Go":
// no implicit fall-through
case "Rust", "C":
// comma-separated cases
default:
}
// `break`, `continue`, and labeled break/continue are available.
// `goto` exists but is rarely appropriate.Functions, methods, and interfaces
func divide(a, b int) (quotient int, err error) {
if b == 0 { return 0, fmt.Errorf("divide by zero") }
return a / b, nil
}
func sum(values ...int) int { // variadic values is []int inside the function
total := 0
for _, v := range values { total += v }
return total
}
func (u User) Label() string { return u.Name } // value receiver
func (u *User) Rename(name string) { u.Name = name } // pointer receiver
// Interfaces are satisfied implicitly: a type needs the required methods only.
type Stringer interface { String() string }
func printAny(v any) {
switch x := v.(type) { // type switch
case string:
fmt.Println(x)
case int:
fmt.Println(x)
default:
fmt.Printf("%T\n", x)
}
}
// `any` is an alias for interface{}; use a narrower interface when possible.
// Assertion: text, ok := v.(string). A failed assertion without `ok` panics.Generics
type Number interface { ~int | ~int64 | ~float64 }
func Sum[T Number](xs []T) T {
var total T
for _, x := range xs { total += x }
return total
}~int includes named types whose underlying type is int. Type parameters may be constrained by interfaces; generic methods are not allowed to introduce their own type parameters.
Errors and resource safety
Errors are ordinary values. Return them; add context with %w; inspect with errors.Is/errors.As.
if err != nil {
return fmt.Errorf("load user %d: %w", id, err)
}
if errors.Is(err, context.Canceled) { /* cancellation is expected */ }
var pathErr *os.PathError
if errors.As(err, &pathErr) { /* handle a typed error */ }Use defer immediately after acquiring a resource: defer file.Close(), defer mu.Unlock(). Deferred calls run LIFO and capture arguments at the defer statement. panic is for unrecoverable programmer/invariant failures, not routine validation. recover only works in a deferred function in the same goroutine.
For untrusted filesystem paths, validate at the boundary (filepath.IsLocal, filepath.Localize) and, on Go 1.24+, consider os.OpenRoot/Root.OpenInRoot to constrain access to a directory.
Concurrency
go f(x)startsfin a goroutine; it shares memory with the caller.- A channel communicates values and synchronizes:
make(chan T), bufferedmake(chan T, n). - The sender normally closes a channel; receiving from a closed channel yields the zero value and
ok == false. selectwaits for one ready channel operation;case <-ctx.Done()handles cancellation.- Never copy a
sync.Mutexafter first use. Protect shared state or use channels/atomics. sync.WaitGroupjoins goroutines;sync.Onceinitializes once;sync.Poolis for temporary reusable objects;sync.Mapis specialized, not a default map replacement.- Use
sync/atomicfor small independent counters/state. Rungo test -race ./...; a race is a correctness bug. - Every goroutine needs a termination path. Pass
context.Contextfirst, cancel derived contexts, and avoid goroutine/channel leaks. - A nil channel blocks forever; setting a channel variable to nil can disable a
selectcase. Sending on a closed channel panics; closing twice panics.select { default: }is non-blocking.
Compilable example: cancellable worker pool, errors, generics, and JSON
Save as main.go in a module and run go run . (works with current Go releases).
package main
import (
"context"
"encoding/json"
"errors"
"fmt"
"sync"
"sync/atomic"
"time"
)
type Job struct{ ID int; Text string }
type Result struct{ ID int; Length int; Err error }
// A generic helper: it works for any element type.
func Map[T, U any](xs []T, f func(T) U) []U {
out := make([]U, len(xs))
for i, x := range xs { out[i] = f(x) }
return out
}
func worker(ctx context.Context, jobs <-chan Job, results chan<- Result, processed *atomic.Int64, wg *sync.WaitGroup) {
defer wg.Done()
for {
select {
case <-ctx.Done():
return
case job, ok := <-jobs:
if !ok { return }
if job.Text == "" {
results <- Result{ID: job.ID, Err: errors.New("empty text")}
continue
}
// Simulate I/O while remaining cancellable.
select {
case <-ctx.Done():
return
case <-time.After(1 * time.Millisecond):
}
processed.Add(1)
results <- Result{ID: job.ID, Length: len([]rune(job.Text))}
}
}
}
func run(ctx context.Context, jobs []Job, workerCount int) ([]Result, int64, error) {
ctx, cancel := context.WithCancel(ctx)
defer cancel()
jobCh := make(chan Job)
resultCh := make(chan Result)
var processed atomic.Int64
var wg sync.WaitGroup
wg.Add(workerCount)
for i := 0; i < workerCount; i++ {
go worker(ctx, jobCh, resultCh, &processed, &wg)
}
go func() {
defer close(jobCh)
for _, job := range jobs {
select {
case jobCh <- job:
case <-ctx.Done():
return
}
}
}()
go func() {
wg.Wait()
close(resultCh)
}()
results := make([]Result, 0, len(jobs))
for result := range resultCh {
results = append(results, result)
if result.Err != nil {
cancel() // stop remaining work after the first bad job
}
}
for _, result := range results {
if result.Err != nil {
return results, processed.Load(), fmt.Errorf("job %d: %w", result.ID, result.Err)
}
}
return results, processed.Load(), nil
}
func main() {
jobs := []Job{{1, "gopher"}, {2, "并发"}, {3, ""}}
results, processed, err := run(context.Background(), jobs, 2)
payload, marshalErr := json.Marshal(results)
if marshalErr != nil { panic(marshalErr) }
fmt.Printf("processed=%d results=%s err=%v\n", processed, payload, err)
lengths := Map([]string{"go", "is", "fun"}, func(s string) int { return len(s) })
fmt.Println("lengths:", lengths)
}The example demonstrates receive-only/send-only channel types, cancellation, channel ownership/closing, WaitGroup, atomic state, a generic function, multiple returns, wrapped errors, Unicode rune counting, and JSON. It intentionally reports the empty job as an error while shutting down workers cleanly.
Standard-library map
- Text/data:
strings,bytes,strconv,unicode/utf8,encoding/json,encoding/csv,regexp. - Files/paths:
io,os,path/filepath,bufio,embed. - HTTP/networking:
net/http,net/url,net,httptest; always set timeouts on clients/servers. - Time/context:
time,context; pass context through call chains, do not store it in structs. - Collections/algorithms:
sort,slices,maps,container/heap. - System/concurrency:
sync,sync/atomic,runtime,os/signal. - Observability:
log/slog,expvar,runtime/pprof,net/http/pprof.
Testing, performance, and design checklist
func TestSum(t *testing.T) {
got := Sum([]int{1, 2, 3})
if got != 6 { t.Fatalf("got %d, want 6", got) }
}Use table-driven tests, subtests (t.Run), benchmarks (go test -bench .), fuzz tests (go test -fuzz=.), and httptest for HTTP. Prefer black-box package tests (package x_test) when testing public behavior. Benchmark before optimizing; use -benchmem, pprof, and allocation profiles. Avoid unnecessary allocations; use strings.Builder for repeated string construction, but favor clarity first.
Idioms: small interfaces owned by consumers; accept interfaces and return concrete types; composition/embedding over inheritance; keep constructors validating invariants; make zero values useful; avoid global mutable state; document exported APIs; use defer for cleanup; never ignore an error without a deliberate reason; keep goroutine ownership and shutdown explicit.