Go Functions
Purpose
Functions are first-class values in Go. They are the primary unit of code organisation and support multiple return values, closures, variadic arguments, and deferred execution.
Architecture
Go functions are block-scoped. Variables declared inside a block (function, loop, if, switch) are inaccessible outside it. Go passes all basic types by value — the callee receives a copy unless a pointer is passed explicitly.
Implementation Notes
Signature — type comes after name
func sub(x int, y int) int {
return x - y
}
// Consecutive same-type params can share the type annotation
func add(x, y int) int {
return x + y
}Multiple Return Values
func divide(a, b float64) (float64, error) {
if b == 0 {
return 0, errors.New("division by zero")
}
return a / b, nil
}
result, err := divide(10, 2)Named Return Values
Named returns document intent and allow bare return:
func calculator(a, b int) (mul, div int, err error) {
if b == 0 {
return 0, 0, errors.New("can't divide by zero")
}
mul = a * b
div = a / b
return // naked return — returns mul, div, err
}Use naked returns only in short functions; they hurt readability in longer ones.
Ignoring Return Values
x, _ := getPoint() // discard the second return valueThe compiler rejects unused variables, so _ is required when you don’t need a value.
Variadic Functions
func sum(nums ...int) int {
total := 0
for _, n := range nums {
total += n
}
return total
}
sum(1, 2, 3) // 6
nums := []int{1,2,3}
sum(nums...) // spread a sliceFunctions as First-Class Values
func aggregate(a, b, c int, op func(int, int) int) int {
return op(op(a, b), c)
}
func add(x, y int) int { return x + y }
aggregate(2, 3, 4, add) // 9Anonymous Functions & Closures
double := func(n int) int { return n * 2 }
// Closure captures outer variable
adder := func(base int) func(int) int {
return func(n int) int { return base + n }
}
add5 := adder(5)
add5(3) // 8Defer
defer schedules a function call to execute just before the enclosing function returns. Arguments are evaluated immediately; execution is deferred. Multiple defers run in LIFO order.
func readFile(path string) error {
f, err := os.Open(path)
if err != nil {
return err
}
defer f.Close() // runs when readFile returns, regardless of which return
// ... use f ...
return nil
}Use defer for cleanup: closing files, releasing locks, logging exit.
Early Returns / Guard Clauses
Prefer early returns over nested conditionals:
func divide(dividend, divisor int) (int, error) {
if divisor == 0 {
return 0, errors.New("can't divide by zero")
}
return dividend / divisor, nil
}Pass by Value
func increment(x int) { x++ } // does NOT affect caller
func main() {
x := 5
increment(x)
fmt.Println(x) // 5
}Use go_pointers when the function needs to mutate the caller’s variable.
Trade-offs
- Naked returns reduce boilerplate but harm readability in long functions — avoid them there.
deferoverhead is small but measurable in hot paths.- Closures capture variables by reference; mutating a captured variable in a goroutine requires care.
- Variadic functions accept a slice spread with
..., but cannot mix variadic and individual args after the spread.