Named Return Values and Errors in Go

Nicholas Cancelliere
ozmox
Published in
3 min readJul 4, 2017

--

When you are working with functions in Go it is likely that you’ll want to handle bad input and report this out in some fashion. This is where the errors package comes into play.

This package provides a simple error handling primitive that you can use in your code. It allows you to add context to a failure state without destroying the original value of the error.

A common idiom you’ll see in Go code:

if err != nil {
return err
end

Consider this function to calculate the square root of a number. We want to return an error if the input is a negative value:

func calculateSquare(f float64) (float64, error) {
if (f < 0) {
return float64(math.NaN()),
errors.New("Float value must be a positive number")
}
return math.Sqrt(f), nil
}

Breaking it down, this function accetps a float64 and returns a tuple comprised of a float64 and an error. If the float is less than zero we return early with a NaN (float) and a new error object, otherwise we just return the result of the square using Go’s math library and nil.

For simple return values just passing a small tuple is probably ok. For more complex return values you might want to use named return values. This will allow you to not worry about the ordering of values you’re returning. Below is the same function only using named return values:

func calculateSquare(f float64) (result float64, err error) {
if (f < 0) {
err = errors.New("Float value must be a positive number")
result = float64(math.NaN())
} else {
result = math.Sqrt(f)
}
return
}

This code is more understandable and with the return variable assignments we don’t have to worry about connascence (of position). We also don’t have to remember to set the nil error return value for the successful path. Go automatically initializes named return values to a zero-value state, so this is why we don’t have to explicitly set err = nil above as we did in the first example.

We could just let the math library attempt the operation, if we did so the result of a negative value would be NaN, and we could evaluate that in our conditional.

func calculateSquare2(f float64) (result float64, err error) {
result = math.Sqrt(f)
if (math.IsNaN(result)) {
err = errors.New("Float value must be a positive number!")
}
return
}

The reason I didn’t choose to do this is:

  1. We don’t really know why the result was NaN, this code assumes it is because the float wasn’t positive. (Is that the only use case that can make a NaN?)
  2. We don’t want to perform an operation if we know ahead of time, with 100% confidence, it will fail and simple validation can be used to test.

When we provide errors we do so because we want to add context to our failed state. Knowing it failed specifically because the value was negative is better than assuming that was why or not knowing specifically at all.

Now with this function we can pass its result to another function whose job it is to print the result (this is where the idiom comes into play):

func printCalcResult(result float64, err error) {
if (err != nil) {
fmt.Printf("ERROR: %f, %s!\n", result, err)
} else {
fmt.Printf("The square is %f.\n", result)
}
}

The output of the function then becomes (depending on what value is provided):

# Calculating for 3.423
The square is 1.850135.

# Calculating for -23.44
ERROR: NaN, Float value must be a positive number!

And there you have it, in a nutshell, a basic introduction to errors and named return values. Happy hacking!

--

--

Software engineering manager living in ATX / Foodie / Gamer / Explorer