13. Multiple return values
Most languages let a function hand back exactly one thing. Go lets it hand back several — and that single decision shapes the way all Go code is written, especially error handling.
Two results instead of one
package main
import "fmt"
func minMax(a, b int) (int, int) {
if a < b {
return a, b
}
return b, a
}
func main() {
lo, hi := minMax(9, 4)
fmt.Println("low:", lo, "high:", hi)
}
The result types go in parentheses: (int, int). The return statement
lists both values, and the caller receives both with a single :=.
No wrapper struct, no out-parameters, no tuple type you have to unpack — just two values.
The pattern that runs Go: (value, error)
Here is the convention you will see in essentially every Go library ever written. A function that might fail returns its result and an error:
package main
import (
"errors"
"fmt"
)
func divide(a, b float64) (float64, error) {
if b == 0 {
return 0, errors.New("division by zero")
}
return a / b, nil
}
func main() {
result, err := divide(10, 4)
if err != nil {
fmt.Println("failed:", err)
} else {
fmt.Println("result:", result)
}
_, err = divide(1, 0)
if err != nil {
fmt.Println("failed:", err)
}
}
Read the shape carefully, because you'll type it a thousand times:
- On failure, return the zero value for the result plus a non-nil error.
- On success, return the real result plus
nil. - The caller checks
if err != nilimmediately, before touching the result.
Go has no exceptions for ordinary failures. Nothing is thrown past you;
errors are values you get back and deal with on the next line. Module 7 goes
deep on what an error actually is — for now, just get comfortable with the
two-value handshake.
Ignoring a result with _
Sometimes you genuinely don't want one of the values. The blank identifier
_ throws it away:
package main
import (
"fmt"
"strconv"
)
func main() {
n, _ := strconv.Atoi("42")
fmt.Println("n is", n)
_, err := strconv.Atoi("not a number")
fmt.Println("err is:", err)
}
strconv.Atoi ("ASCII to integer") is itself a (value, error) function —
the standard library follows the same convention your code does.
Use _ when you have thought about the value and decided you don't need it.
Discarding an err with _ is a code smell in real projects: it's how bugs
hide. Discarding it is a decision, not a shortcut.
Named results
You can name the results in the signature. The names become ordinary variables, pre-initialised to their zero values:
package main
import "fmt"
func split(total int) (hours, minutes int) {
hours = total / 60
minutes = total % 60
return hours, minutes
}
func main() {
h, m := split(195)
fmt.Printf("%dh %dm\n", h, m)
}
Named results serve one purpose better than any other: documentation.
func split(total int) (hours, minutes int) tells you which number is which,
while (int, int) leaves you guessing.
Go also allows a bare return with named results — it returns whatever the
named variables currently hold. It's legal, and it's how deferred functions
modify a return value (a trick you'll meet in the errors module), but in
ordinary code, spell out what you're returning. A bare return at the bottom
of a long function is a puzzle for the reader.
Three is fine, six is a smell
Nothing stops you returning more:
package main
import "fmt"
func stats(nums []int) (sum, count, max int) {
for i, n := range nums {
sum += n
count++
if i == 0 || n > max {
max = n
}
}
return sum, count, max
}
func main() {
s, c, m := stats([]int{3, 9, 4})
fmt.Println("sum:", s, "count:", c, "max:", m)
}
But past two or three, callers start losing track of the order. When you get there, return a struct instead and give every field a name — that's what the Structs module is for.
Your turn
Write applyTax that takes a float64 price and returns the tax amount
(10% of price) and the total price, in that order. Print them with two
decimals so the program outputs exactly:
tax 5.00, total 55.00
package main
import "fmt"
// write applyTax here — it returns (tax, total)
func main() {
tax, total := applyTax(50)
fmt.Printf("tax %.2f, total %.2f\n", tax, total)
}
package main
import "fmt"
func applyTax(price float64) (float64, float64) {
tax := price * 0.10
return tax, price + tax
}
func main() {
tax, total := applyTax(50)
fmt.Printf("tax %.2f, total %.2f\n", tax, total)
}
Next: functions that accept any number of arguments.