Engineering Blog

Unintended side effects with named result parameters

In this blog we are going to know about unintended side effects with named result parameters. We know that named result parameters can be useful in some situations. But as these result parameters are initialized to their zero value, using them can sometimes lead to subtle bugs if we’re not careful enough.

Let us look at such a case. Let us consider, a method that returns the latitude and longitude from a given address. Because we return two float32s, we decide to use named result parameters to make the latitude and longitude explicit. This function will first validate the given address and then get the coordinates. In between, it will perform a check on the input context to make sure it wasn’t canceled and that its deadline hasn’t passed.

A context can carry a cancellation signal or a deadline. We can check those by calling the Err method and testing that the returned error isn’t nil.

func (l loc) getCoordinates(ctx context.Context, address string) (lat, lng float32, err error){
isValid := l.validateAddress(address)
if !isValid {
return 0, 0, errors.New("invalid address")
}
if ctx.Err() != nil {
return 0, 0, err
}
// Get and return coordinates
}

The error might not be obvious at first glance. Here, the error returned in the if ctx.Err() != nil scope is err. But we haven’t assigned any value to the err variable. It’s still assigned to the zero value of an error type: nil. Hence, this code will always return a nil error. Furthermore, this code compiles because err was initialized to its zero value due to named result parameters. Without attaching a name, we would have gotten the following compilation error:

Unresolved reference 'err'

One possible fix is to assign ctx.Err() to err like so:

if err := ctx.Err(); err != nil {
return 0, 0, err
}

We keep returning err, but we first assign it to the result of ctx.Err(). Note that err in this example shadows the result variable. Using a naked return statement Another option is to use a naked return statement:

if err = ctx.Err(); err != nil {
return
}

However, doing so would break the rule stating that we shouldn’t mix naked returns and returns with arguments. In this case, we should probably stick with the first option. Remember that using named result parameters doesn’t necessarily mean using naked returns.

Conclusion

Sometimes we can just use named result parameters to make a signature clearer. We conclude this discussion by emphasizing that named result parameters can improve code readability in some cases (such as returning the same type multiple times) and be quite handy in others. But we must recall that each parameter is initialized to its zero value. As we have seen in this section, this can lead to subtle bugs that aren’t always straightforward to spot while reading code. Therefore, let’s remain cautious when using named result parameters, to avoid potential side effects.

References:

  • 100 Go Mistakes and how to avoid them, Teiva Harsanyi, Manning Publications Co
Previous Post
Next Post