Multiple Return Values

Go hands back a result and an error together instead of throwing exceptions, and the caller has to take both.

Syntaxfunc parse(s string) (int, error)

A Go function can hand back more than one value. The common pair is a result and an error. That pair is how Go reports failure. There is no throwing and catching: a function that can fail says so in its signature, and the caller cannot avoid seeing it.

error is a built-in type. Its value is either nil, which means nothing went wrong, or a value carrying a message about what did. nil is Go's word for "no value". By a convention everyone follows, the error is the last result.

func Atoi(s string) (int, error)

strconv.Atoi turns text into a number. It has two results, so the caller writes two variables.

Taking the values

The short form declares and assigns in one line:

n, err := strconv.Atoi("7")

The long form is the same work spelled out, declaring first and assigning after:

var n int
var err error
n, err = strconv.Atoi("7")

Either way, check if err != nil and deal with it before you use n.

What trips people up

You cannot quietly take one of two values. n := strconv.Atoi("7") fails to compile with assignment mismatch. Write _, the blank identifier, to throw a value away on purpose. And when a call fails, its other results hold their zero values, 0 for a number and "" for a string, so a returned 0 tells you nothing until you have looked at the error.

Example

Example · go
package main

import (
	"fmt"
	"strconv"
)

// double hands back two values: the number, and an error.
// The error comes last. nil means nothing went wrong.
func double(text string) (int, error) {
	n, err := strconv.Atoi(text) // Atoi is func(string) (int, error)
	if err != nil {
		return 0, err // 0 is a placeholder the caller must not use
	}
	return n * 2, nil
}

func main() {
	n, err := double("21")
	if err != nil {
		fmt.Println("error:", err)
		return
	}
	fmt.Println("total:", n)

	if _, err := double("twenty"); err != nil {
		fmt.Println("error:", err)
	}
}

// Output:
// total: 42
// error: strconv.Atoi: parsing "twenty": invalid syntax

When to use it

  • Turn text typed into a form into a number, and tell the user when it is not one.
  • Hand back a loaded record and the reason it could not be loaded in a single call.
  • Return a value together with a second value saying whether it was found at all.

More examples

Three results, and building an error

Two results plus an error is normal when a calculation naturally produces a pair, here the whole part and the remainder. Reach for this when splitting the answer across two calls would do the same work twice. Past three or four results, give the values names by returning a struct instead.

Example · go
package main

import (
	"errors"
	"fmt"
)

// divide hands back three values. Any number of results is allowed.
// errors.New builds an error from a fixed message.
func divide(a, b int) (int, int, error) {
	if b == 0 {
		return 0, 0, errors.New("divide by zero")
	}
	return a / b, a % b, nil
}

func main() {
	q, r, err := divide(17, 5)
	if err != nil {
		fmt.Println("error:", err)
		return
	}
	fmt.Println(q, r)

	if _, _, err := divide(1, 0); err != nil {
		fmt.Println("error:", err)
	}
}

// Output:
// 3 2
// error: divide by zero

Short form, long form, and the blank identifier

The two declarations compile to the same thing; the short form is what you will write, the long form is what you need when the variable has to exist before the call, for example outside an if block. The last pair shows why discarding an error is dangerous: ignored is 0, and 0 is also a perfectly good parsed number.

Example · go
package main

import (
	"fmt"
	"strconv"
)

func main() {
	// Short form: := declares both variables and assigns in one line.
	short, err := strconv.Atoi("7")
	if err != nil {
		fmt.Println(err)
		return
	}

	// Long form: the same thing, declared with var and then assigned.
	var long int
	var longErr error
	long, longErr = strconv.Atoi("7")
	if longErr != nil {
		fmt.Println(longErr)
		return
	}

	// _ throws the error away on purpose. ignored is left at 0.
	ignored, _ := strconv.Atoi("not a number")

	fmt.Println(short, long, ignored)
}

// Output:
// 7 7 0

Discussion

  • Be the first to comment on this lesson.