Functions#

Functions in Go are first-class values: they can be stored, passed, returned, and assigned to interface methods. Signatures support multiple return values, named returns, variadic parameters, generic type parameters, and pointer-or-value method receivers.

/oop`. For goroutines launched with go, see Concurrency.

Declaration#

func add(a, b int) int {
    return a + b
}

// multiple return values
func divmod(a, b int) (int, int) {
    return a / b, a % b
}

// named returns
func parsePort(s string) (port int, err error) {
    port, err = strconv.Atoi(s)
    if err != nil { return }              // naked return uses named values
    if port < 1 || port > 65535 {
        err = fmt.Errorf("bad port: %d", port)
    }
    return
}

Use named returns sparingly; they read well for short functions, become noise in long ones.

Parameters#

Parameters of the same type share a single type annotation.

func max(a, b, c int) int { /* … */ return 0 }

Variadic parameters use ...T; the callee sees the arguments as a []T.

func sum(xs ...int) int {
    total := 0
    for _, x := range xs { total += x }
    return total
}

sum(1, 2, 3)
sum([]int{1, 2, 3}...)         // spread a slice

Multiple returns#

The standard return form is (value, error). The operator checks err != nil and returns it (often wrapped) on the unhappy path.

func load(path string) ([]byte, error) {
    data, err := os.ReadFile(path)
    if err != nil {
        return nil, fmt.Errorf("load %q: %w", path, err)
    }
    return data, nil
}

The blank identifier _ discards a return value.

_, err := os.Stat(path)

Closures#

A function literal captures the surrounding locals by reference.

func makeCounter() func() int {
    n := 0
    return func() int {
        n++
        return n
    }
}

next := makeCounter()
fmt.Println(next(), next(), next())   // 1 2 3

Closures over loop variables are now safe per-iteration as of Go 1.22; in older versions the operator copied the variable into a fresh scope inside the loop.

defer#

defer is part of the function surface; see Control flow for the runtime semantics. The operator pairs every resource acquisition with an immediate defer to its release.

func process(path string) error {
    f, err := os.Open(path)
    if err != nil { return err }
    defer f.Close()
    // …
    return nil
}

Generics#

Type parameters in square brackets.

func Head[T any](xs []T) (T, bool) {
    var zero T
    if len(xs) == 0 { return zero, false }
    return xs[0], true
}

n, ok := Head([]int{1, 2, 3})

Constraints describe what the type parameter supports; cmp.Ordered covers all orderable primitives, comparable covers anything usable as a map key.

func Max[T cmp.Ordered](a, b T) T {
    if a > b { return a }
    return b
}

type Numeric interface { ~int | ~int64 | ~float64 }

func Sum[T Numeric](xs []T) T {
    var total T
    for _, x := range xs { total += x }
    return total
}

The ~ prefix means “or any type with this underlying type” (so type Counter int matches ~int).

Method functions#

A method is a function with a receiver. The receiver appears in parentheses before the function name; pointer receivers allow the method to mutate the value.

type Counter struct{ n int }

func (c Counter) Get() int   { return c.n }
func (c *Counter) Inc()      { c.n++ }

var c Counter
c.Inc()                       // Go takes &c implicitly
fmt.Println(c.Get())          // 1

The full surface (method sets, embedding, interface satisfaction) lives in OOP.

Function types as parameters#

Pass behaviour as a parameter; this is how the operator parameterises iteration and filtering.

type Predicate[T any] func(T) bool

func Filter[T any](xs []T, p Predicate[T]) []T {
    out := xs[:0]
    for _, x := range xs {
        if p(x) { out = append(out, x) }
    }
    return out
}

adults := Filter(users, func(u User) bool { return u.Age >= 18 })

init#

func init() {} runs once per package at program startup, after package-level variable initialisation. Multiple init functions per file are allowed; the order across files is governed by lexicographic filename order.

var logger *slog.Logger

func init() {
    logger = slog.New(slog.NewJSONHandler(os.Stdout, nil))
}

Avoid init for anything beyond setting package-private state; long-running setup belongs in an explicit constructor the application calls.

References#