Functions and Multiple Return Values
Declaring functions in Go, and the multiple-return-value pattern that shapes idiomatic error handling.
3 min read
Functions are declared with the func keyword, and Go's syntax puts parameter types after each name rather than before, which takes a moment to get used to coming from C-family languages.
Basic function syntax
func add(a int, b int) int {
return a + b
}When consecutive parameters share a type, you can collapse them:
func add(a, b int) int {
return a + b
}add(a, b int) reads as "a and b are both ints" — the type only needs to be written once for the trailing group that shares it.
Multiple return values
This is one of Go's most distinctive and most-used features: a function can return more than one value, with no tuple type or wrapper object needed.
func divide(a, b int) (int, error) {
if b == 0 {
return 0, errors.New("cannot divide by zero")
}
return a / b, nil
}Calling it, you receive both values directly:
result, err := divide(10, 2)
if err != nil {
fmt.Println("error:", err)
return
}
fmt.Println("result:", result)This (value, error) pattern is the idiom in Go for anything that can fail — file reads, network calls, parsing, database queries. Rather than exceptions bubbling up invisibly through the call stack, every fallible call makes its failure explicit and impossible to silently ignore without deliberately writing _ for the error.
Named return values
Return values can be given names in the function signature, which pre-declares them as variables inside the function body:
func divide(a, b int) (result int, err error) {
if b == 0 {
err = errors.New("cannot divide by zero")
return
}
result = a / b
return
}A bare return sends back whatever result and err currently hold. This can make the intent of a function clearer at a glance, especially combined with defer (as in the previous lesson's recover example), though most Go style guides recommend using named returns sparingly — they're most useful when a deferred function needs to modify the return values, and can otherwise obscure what's actually being returned in a longer function body.
Ignoring return values
Any return value can be discarded with the blank identifier _:
result, _ := divide(10, 2) // ignoring the error -- usually a bad idea!You can do this, but idiomatic Go treats an ignored error as a code smell worth a second look — a linter like errcheck will flag it, and reviewers will ask about it.
Functions as values
Functions in Go are first-class values — they can be assigned to variables, passed as arguments, and returned from other functions:
func apply(fn func(int, int) int, a, b int) int {
return fn(a, b)
}
sum := apply(add, 3, 4) // 7func(int, int) int is itself a type — "a function taking two ints and returning an int" — and any function matching that shape can be passed in. This is the foundation for closures (next lesson) and for passing behavior around the way you'd pass any other value.
Test what you just learned
4 quick questions. Get all of them right to unlock the next lesson.
You can take the quiz without an account — logging in just lets your result count toward your progress.