Multiple Return Values
A Go function can return more than one value. This simple feature shapes the whole language: instead of exceptions, functions return their result together with an error, and instead of out-parameters, they just return everything the caller needs. This lesson covers returning and receiving several values, the value, err convention, named results, and when to return a struct instead.
Returning several values
List the result types in parentheses, and return the values separated by commas:
package main import "fmt" func minMax(nums []int) (int, int) { lo, hi := nums[0], nums[0] for _, n := range nums[1:] { lo = min(lo, n) hi = max(hi, n) } return lo, hi } func divmod(a, b int) (int, int) { return a / b, a % b } func main() { lo, hi := minMax([]int{7, 2, 9, 4}) fmt.Println(lo, hi) q, r := divmod(17, 5) fmt.Printf("17 = 5*%d + %d\n", q, r) _, hi = minMax([]int{1, 50, 3}) // ignore one with _ fmt.Println(hi) }
2 9 17 = 5*3 + 2 50
You must receive all the results or none: lo := minMax(…) doesn't compile. Use _ for the ones you don't need.
The value, error convention
The most common use of multiple results is returning a value together with an error. The error comes last; if it's nil, the operation succeeded and the value is meaningful. If it isn't, the caller should handle the error and usually ignore the value:
package main import ( "errors" "fmt" ) func averageOf(nums []float64) (float64, error) { if len(nums) == 0 { return 0, errors.New("no numbers to average") } sum := 0.0 for _, n := range nums { sum += n } return sum / float64(len(nums)), nil } func main() { avg, err := averageOf([]float64{4, 8, 9}) if err != nil { fmt.Println("error:", err) return } fmt.Println("average:", avg) if _, err := averageOf(nil); err != nil { fmt.Println("error:", err) } }
average: 7 error: no numbers to average
The pattern result, err := f(); if err != nil { … } appears in nearly every Go function. Lesson 44 covers errors in depth.
The comma-ok idiom
Another convention uses a bool as the second result to say whether the first is valid — like the map lookup you've already seen. It suits "not found" situations that aren't really errors:
package main import "fmt" var capitals = map[string]string{"France": "Paris", "Japan": "Tokyo"} func capital(country string) (string, bool) { c, ok := capitals[country] return c, ok } func main() { for _, country := range []string{"Japan", "Atlantis"} { if c, ok := capital(country); ok { fmt.Println(country, "->", c) } else { fmt.Println(country, "-> unknown") } } }
Japan -> Tokyo Atlantis -> unknown
Passing results straight on
When a function's results match another function's parameters exactly, you can pass the call directly:
package main import "fmt" func dimensions() (float64, float64) { return 3, 4 } func area(w, h float64) float64 { return w * h } func main() { fmt.Println(area(dimensions())) fmt.Println(dimensions()) // Println accepts any number of values }
12 3 4
Named results
Results can be given names. They start as zero values, document what each result means, and a bare return returns their current values:
package main import ( "fmt" "strings" ) func splitName(full string) (first, last string) { parts := strings.Fields(full) if len(parts) == 0 { return // returns "", "" } first = parts[0] if len(parts) > 1 { last = parts[len(parts)-1] } return } func main() { f, l := splitName("Grace Brewster Hopper") fmt.Printf("%q %q\n", f, l) f, l = splitName("Plato") fmt.Printf("%q %q\n", f, l) }
"Grace" "Hopper" "Plato" ""
Named results are most useful as documentation when a function returns two values of the same type, such as (first, last string). Bare returns save typing but make longer functions harder to read, because you have to look elsewhere to see what's returned — use them only in short functions. Named results also let a deferred function change what's returned (lesson 34).
When to return a struct instead
Two or three results are fine. Beyond that, callers have to remember the order and it's easy to mix up values of the same type. Group related results into a struct (lesson 36):
package main import "fmt" type Stats struct { Count int Min, Max, Sum float64 } func describe(nums []float64) Stats { s := Stats{Count: len(nums), Min: nums[0], Max: nums[0]} for _, n := range nums { s.Min, s.Max, s.Sum = min(s.Min, n), max(s.Max, n), s.Sum+n } return s } func main() { st := describe([]float64{3.5, 9, 1.25}) fmt.Printf("%+v\n", st) fmt.Println("mean:", st.Sum/float64(st.Count)) }
{Count:3 Min:1.25 Max:9 Sum:13.75}
mean: 4.583333333333333Try it yourself
Write a function that parses "width x height" text such as "1920x1080" and returns both numbers and an error.
1920x1080 -> 1920 1080 <nil> 800-600 -> 0 0 missing 'x' 64xabc -> 0 0 strconv.Atoi: parsing "abc": invalid syntax