package main // one per file
import (
"fmt"
"os"
"github.com/me/pkg" // own/3rd-party
)
const Name = "sdse"
type T struct{ N int }
func (t T) String() string { return t.N }
func main() { fmt.Println(os.Args) }
Hard rules: unused import or local var = compile error. Opening brace on the same line (semicolons are auto-inserted). One package per directory. No cyclic imports.
| package import | file header |
| func var const type | declare |
| struct interface map chan | composite types |
| if else switch case default fallthrough | branch |
| for range break continue goto | loop / jump |
| return | exit function |
| go select defer | concurrency / cleanup |
Not keywords (shadowable, don't): true false nil iota any comparable error,
type names, and the built-in functions.
var x int // zero value 0
var y = 42 // inferred
var z int = 42
w := 42 // func bodies ONLY
var (
name string
tries = 3
)
a, b := 1, "two" // multiple
a, err := f() // := needs 1 new name
a, b = b, a // swap: RHS evaluated first
Shadowing: err := ... inside an if/for body makes a
new err that dies at the brace. Top cause of vanishing errors.
| 0 | all numerics |
| false | bool |
| "" | string (never nil) |
| nil | pointer slice map chan func interface |
| {...} | struct/array: each element zeroed |
Design so the zero value works: sync.Mutex, bytes.Buffer,
strings.Builder all need no constructor.
const Pi = 3.14159 // untyped: adapts
const S string = "hi" // typed
// const t = time.Now() // ERROR: runtime value
type Level int
const (
Debug Level = iota // 0, resets per block
Info // 1 (expr repeats)
Warn // 2
)
const (
_ = iota
KB = 1 << (10 * iota) // 1024
MB // 1048576
)
Only bool, string, numbers. Untyped consts carry arbitrary precision until used.
int uint int8..64 uint8..64 uintptr
float32 float64 complex64 complex128
bool string
byte = uint8 rune = int32
any = interface{} // 1.18+
error // an INTERFACE
f := float64(i) // ALWAYS explicit
n, err := strconv.Atoi("42") // str<->num
var x int32; var y int64
z := int64(x) + y // no mixed arithmetic
string(65) is "A", not "65" — use strconv.Itoa.
uint8(300) wraps silently to 44.
s := "Gø"
len(s) // 3 — BYTES not chars
s[0] // 71 — a byte
utf8.RuneCountInString(s) // 2 — chars
[]rune(s); []byte(s) // allocates
for i, r := range s { } // decodes UTF-8;
// i jumps by rune width
var b strings.Builder // concat in a loop
b.WriteString("x")
b.String()
Immutable: s[0]='H' won't compile. += in a loop reallocates every pass.
var s []int // nil, but append-able
s = append(s, 1, 2) // ALWAYS reassign
t := []int{1,2,3,4,5}
u := make([]int, 0, 10) // len 0, cap 10
t[1:3] // [2 3] half-open t[:2] t[2:]
copy(dst, src) // min(len) elems
clear(s) // zero all (1.21+)
slices.Contains/Sort/Clone/Index (1.21+)
view := orig[1:3] // SHARES backing array
view[0] = 99 // orig changes too
view = append(view, 7) // writes through if
// cap allows, else
// reallocates+detaches
Aliasing depends on capacity → bugs are intermittent. Need independence?
slices.Clone(v) or three-index v[1:3:3].
m := map[string]int{"a": 1}
n := make(map[string]int)
var p map[string]int // nil: reads OK,
// WRITES PANIC
m["b"] = 2
v := m["nope"] // 0, no error
v, ok := m["nope"] // comma-ok
delete(m, "a") // no-op if absent
len(m); clear(m) (1.21+)
for k, v := range m { } // RANDOM ORDER
slices.Sorted(maps.Keys(m)) (1.23+)
Keys must be comparable (no slice/map/func). Set = map[K]struct{} (0 bytes per value).
p := &x // *int
*p = 20 // write through
q := new(int) // zeroed, returns *int
var r *int // nil: *r panics
func byVal(c Counter) { c.n++ } // copy
func byPtr(c *Counter) { c.n++ } // original
byPtr(&c)
| copied | num bool string array struct |
| header copied, data shared | slice map chan func interface |
No pointer arithmetic. Returning &Local{} is safe — escape analysis decides heap vs stack.
if v, err := f(); err != nil { // init stmt
} else if x > 5 {
} else { }
for i := 0; i < 10; i++ { } // C-style
for x < 100 { } // while
for { } // infinite
for i, v := range xs { }
for k, v := range m { } // random order
for i, r := range s { } // string: runes
for v := range ch { } // until closed
for i := range 10 { } // 1.22+
for v := range seq { } // 1.23+ iterator
outer:
for i := range g {
for j := range g[i] {
break outer // or continue outer
}
}
1.22+: loop vars are per-iteration, so closing over them in a goroutine is now safe.
switch day {
case "sat", "sun": // multiple values
x()
case "mon":
fallthrough // explicit, rare
default:
}
switch { // = if/else chain
case n >= 90: g = "A"
case n >= 80: g = "B"
}
switch v := x.(type) { // TYPE switch
case nil:
case int: use(v * 2) // v is int
case string: use(len(v)) // v is string
case error: return v
default: fmt.Printf("%T", v)
}
No implicit fallthrough. x must be an interface value in a type switch.
func add(a, b int) int { return a + b }
func div(a, b float64) (float64, error) {
if b == 0 { return 0, errors.New("÷0") }
return a / b, nil
}
func split(sum int) (x, y int) { // named
x = sum / 2
y = sum - x
return // naked
}
func sum(nums ...int) int { } // variadic
sum(1,2,3); sum(xs...) // spread
double := func(x int) int { return x*2 }
func counter() func() int { // closure
n := 0
return func() int { n++; return n }
}
Multiple returns are why Go needs no exceptions. Variadic param must be last.
f, err := os.Open(p)
if err != nil { return err }
defer f.Close() // runs on EVERY exit
mu.Lock()
defer mu.Unlock()
x := 1
defer fmt.Println(x) // prints 1: ARGS
x = 2 // EVALUATED NOW
for i := 0; i < 3; i++ {
defer fmt.Print(i) // LIFO -> 210
}
func t() (n int, err error) {
defer func() {
if err != nil { n = 0 } // can rewrite
}() // named results
return doWork()
}
Runs at function return, not block end — never defer in a long loop.
type Celsius float64 // NEW distinct type
type Temp = float64 // ALIAS, same type
func (c Celsius) String() string {
return fmt.Sprintf("%.1f°C", float64(c))
}
c = Celsius(f) // conversion needed
| definition | distinct; starts with no methods; you add your own |
| alias | interchangeable; shares methods; can't add |
A defined type keeps the underlying type's operators but inherits no methods. Any type can have methods — not just structs.
type Student struct {
Name string
Email string
Courses []string
active bool // unexported
}
s := Student{Name: "Ada"} // KEYED always
p := &Student{Name: "Ada"}
p.Name // auto-deref, no ->
type Point struct{ X, Y int }
Point{1,2} == Point{1,2} // true, field-wise
// (slice field = no ==)
type Empty struct{} // 0 bytes
done := make(chan struct{})
Name string `json:"name" db:"full_name"`
Email string `json:"email,omitempty"`
Age int `json:"-"` // never marshalled
Tag typos fail silently — run go vet.
func (c Counter) Value() int { return c.n }
func (c *Counter) Inc() { c.n++ }
c.Inc() // = (&c).Inc(), compiler helps
p.Value() // = (*p).Value()
| pointer | mutates; large struct; holds a mutex |
| value | small/immutable; map, slice, basic type |
Method sets: T has only value-receiver methods; *T has both.
So *Counter satisfies an interface needing Inc() but Counter does
not — interface satisfaction gets no auto-address help.
Be consistent: if one method needs a pointer, give them all pointers.
// no constructors; convention is NewT
func NewCounter(start int) *Counter {
return &Counter{n: start}
}
func NewStudent(n string) (*Student, error) {
if n == "" {
return nil, errors.New("name required")
}
return &Student{Name: n}, nil
}
// one main type per pkg? call it New()
type Shape interface {
Area() float64
Perimeter() float64
}
type ReadWriter interface { // embed
io.Reader
io.Writer
}
// satisfied IMPLICITLY — no "implements"
func (c Circle) Area() float64 { }
func (c Circle) Perimeter() float64 { }
var s Shape = Circle{R: 2}
var _ Shape = (*Circle)(nil) // compile check
s := x.(string) // panics if wrong
s, ok := x.(string) // comma-ok
if sh, ok := x.(Shape); ok { }
Accept interfaces, return structs. Define them where consumed. Keep them tiny
(io.Reader = 1 method). Don't write one until there are 2 implementations.
Nil trap: an interface is nil only if type and value are nil. Returning a nil
*MyError as error makes err != nil TRUE.
type Employee struct {
Person // no field name
Company string
}
e := Employee{Person: Person{Name:"Ada"}}
e.Name // promoted field
e.Greet() // promoted method
e.Person.Greet() // long form
func (e Employee) Greet() string { // shadow
return e.Person.Greet() + " @" + e.Company
}
type LoggingStore struct {
Store // embed INTERFACE:
log *slog.Logger // wrap one method,
} // delegate the rest
Not polymorphism: a Person method calling Greet() always gets
Person.Greet. No virtual dispatch to the outer type — use an interface for that.
func Map[T, U any](xs []T, f func(T) U) []U {
out := make([]U, 0, len(xs))
for _, x := range xs { out = append(out, f(x)) }
return out
}
Map(xs, func(s string) int { return len(s) })
type Number interface {
~int | ~float64 // ~ = underlying type
}
func Sum[T Number](xs []T) T {
var total T // zero of T
for _, x := range xs { total += x }
return total
}
type Stack[T any] struct{ items []T }
func (s *Stack[T]) Push(v T) { }
st := &Stack[int]{}
| any | anything |
| comparable | == != |
| cmp.Ordered | < > (1.21+) |
Check slices/maps first. Interface = behaviour varies; generic = same logic,
different type.
type error interface { Error() string }
errors.New("not found")
fmt.Errorf("load %s: %v", p, err) // no wrap
fmt.Errorf("load %s: %w", p, err) // WRAPS
var ErrNotFound = errors.New("not found")
type ValidationError struct{ Field string }
func (e *ValidationError) Error() string { }
errors.Is(err, ErrNotFound) // sentinel
var ve *ValidationError
errors.As(err, &ve) // + binds ve
errors.Unwrap(err) // one level
errors.Join(e1, e2) (1.20+)
Never err == ErrX (breaks on wrap) and never match on
err.Error() text.
Lowercase, no trailing punctuation. Add context going up. Handle once: log or return, not both.
panic("unreachable") // unwinds, runs defers
func safe() (err error) {
defer func() {
if r := recover(); r != nil {
err = fmt.Errorf("recovered: %v", r)
}
}() // ONLY works in a defer
risky()
return nil
}
| nil deref | index out of range |
| bad type assert | write to nil map |
| divide by zero | close closed/nil chan |
OK to panic: impossible states, MustCompile-style init, per-request recovery in a server.
recover does not cross goroutines — an unrecovered panic in any goroutine
kills the process.
go doWork() // returns immediately
go func() { }() // args eval'd at `go`
var wg sync.WaitGroup
for _, u := range urls {
wg.Add(1) // BEFORE go
go func() {
defer wg.Done()
fetch(u)
}()
}
wg.Wait()
wg.Go(func(){ fetch(u) }) // 1.25+
| Mutex | Lock/Unlock + defer |
| RWMutex | many readers, 1 writer |
| Once | once.Do(f) |
| atomic | Int64, CompareAndSwap |
| errgroup | WaitGroup + first error |
When main returns, every goroutine dies mid-flight. Nothing waits for you.
Run go test -race ./... — put it in CI.
ch := make(chan int) // UNBUFFERED
buf := make(chan int, 10) // buffered
var nilCh chan int // blocks forever
ch <- 42 // send
v := <-ch // receive
v, ok := <-ch // ok=false when drained
close(ch) // SENDER closes, only once
for v := range ch { } // until closed
func prod(out chan<- int) { } // send-only
func cons(in <-chan int) { } // recv-only
| op | nil | open | closed |
|---|---|---|---|
| recv | blocks | blocks | zero,false |
| send | blocks | blocks | panic |
| close | panic | ok | panic |
Don't communicate by sharing memory; share memory by communicating.
select {
case v := <-ch1:
case ch2 <- 42:
case <-time.After(2*time.Second):
case <-ctx.Done(): return ctx.Err()
default: // makes it NON-BLOCKING
}
for { // worker loop
select {
case job, ok := <-jobs:
if !ok { return }
handle(job)
case <-ctx.Done():
return
}
}
ctx, cancel := context.WithCancel(parent)
defer cancel() // ALWAYS
ctx, cancel = context.WithTimeout(ctx, 5*time.Second)
func f(ctx context.Context, ...) // first param
Ready cases are picked at random. select{} blocks forever.
Patterns: worker pool · fan-out/fan-in · pipeline · done-channel.
| len cap | length / capacity |
| make(T,n,c) | init slice, map, chan → T |
| new(T) | zeroed alloc → *T |
| append | grow slice (use result!) |
| copy(d,s) | min(len) elements |
| delete(m,k) | remove key |
| clear(v) | empty map / zero slice 1.21 |
| min max | ordered values 1.21 |
| close(ch) | finish a channel |
| panic recover | abort / resume |
| complex real imag | complex numbers |
| print println | stderr, unspecified — use fmt |
make works on exactly 3 types (slice/map/chan) — the ones with runtime internals.
func main() // pkg main, entry
func init() // pre-main, any number
// per file, uncallable
func TestXxx(t *testing.T) // _test.go
func BenchmarkXxx(b *testing.B) // b.N loop
func FuzzXxx(f *testing.F)
func ExampleXxx() // checked if "// Output:"
func TestMain(m *testing.M) // setup/teardown
t.Run/Parallel/Cleanup/Helper/Errorf
| String() | fmt.Stringer — used by %v/%s |
| Error() | error (beats String) |
| Unwrap() | feeds errors.Is/As |
| Read/Write/Close | io.Reader/Writer/Closer |
| ServeHTTP | http.Handler |
| MarshalJSON | json.Marshaler |
| New… | factory |
| Must… | panics instead of erroring |
| Err… / …Error | sentinel value / error type |
| …er | 1-method interface |
| GetX() | never — it's just X() |
_, err := fmt.Println("x") // discard
for _, v := range xs { } // skip index
import _ "github.com/lib/pq" // init() only
var _ Shape = (*Circle)(nil) // assert
type Student struct {
Name string // exported
email string // package-private
}
// visibility = capital letter; the unit of
// encapsulation is the PACKAGE, not the type
import (
"net/http" // -> http
mrand "math/rand" // alias
_ "github.com/lib/pq" // side effects
) // NO dot imports
| cmd/<name>/ | one dir per binary |
| internal/ | import-blocked outside parent |
| testdata/ | ignored by the build |
| *_test.go | test-only compile |
go mod init <path> go mod tidy
go get pkg@v1.2.3 go work init ./a ./b
Names: short, lowercase, singular, no utils. The package name prefixes every call site.
| go run . | compile + run |
| go build ./... | compile all |
| go test ./... | all tests |
| go test -race | race detector |
| go test -v -run TestX | filter by regex |
| go test -cover | coverage |
| go test -bench=. -benchmem | benchmarks |
| go vet ./... | real-bug static checks |
| gofmt -l -w . | canonical format |
| goimports -w . | fmt + fix imports |
| go doc fmt.Println | docs in terminal |
GOOS=linux GOARCH=arm64 go build // cross
CGO_ENABLED=0 go build // static, FROM scratch
go build -ldflags="-X main.version=1.2.3"
CI gate: test -z "$(gofmt -l .)". One style, no options, no arguments.
fmt.Println(a, b) fmt.Printf("%s\n", s)
fmt.Sprintf(...) fmt.Fprintf(w, ...)
fmt.Errorf("ctx: %w", err)
| %v %+v %#v | default · with field names · Go syntax |
| %T | dynamic type |
| %s %q | string · quoted |
| %d %b %o %x | int base 10/2/8/16 |
| %f %.2f %e %g | float forms |
| %t %c %U %p | bool · rune · U+00F8 · pointer |
| %w | wrap (Errorf only) |
| %8.2f %-20s %08d | width · left · zero-pad |
%!d(string=hi) = wrong verb; go vet catches it at build time.
Dropping append's result | append is lost → s = append(...) |
| Write to nil map | panic → make it |
err shadowed by := | failure vanishes |
Typed nil as error | err != nil with no error |
len(s) on non-ASCII | bytes ≠ chars |
err == ErrX | breaks on wrap → errors.Is |
| Map order | randomised on purpose |
defer in a hot loop | handles pile up |
| Sub-slice of a huge array | keeps it all alive → Clone |
string(65) | "A" → strconv.Itoa |
| Unread channel | goroutine leak |
| Receiver closes a channel | panic on next send |
for _, s := range xs + mutate | it's a copy → xs[i].F |
| Value receiver + mutex | copies the lock (vet flags it) |