Kanu
Kanu

Reputation: 493

Is it conventional to return both value and error from function at the same time

I cannot find a good confirmation if returning both non-nil value and non-nil error at the same time is conventional or not. Example:

func makeHTTPCall(...) string, error {
    resp, err := http.Post(...)
    if err != nil {
        return "", err
    }

    var r string
    if resp.StatusCode == http.StatusOK {
        if err := json.NewDecoder(resp.Body).Decode(&r); err != nil {
            return "", err
        }
        if err := resp.Body.Close(); err != nil {
            // should I return error here even if I have successful result?
            // or should I return both non-nil value and non-nil error?
            // or should i just log the error?
        }
        return r, nil
    }
}

Definitely it is allowed by compiler. Why I'm asking is because of the convention to check for error first. If I return both non-nil value and error client code will do the check for error first and will go on error handling path:

str, err := makeHTTPCall(...)
if err != nil {
   throw err
}

Is there any agreement around this?

Upvotes: 4

Views: 5720

Answers (3)

Igor Chubin
Igor Chubin

Reputation: 64573

In general, it is pretty standard way to return errors: if a non-nil error is returned, all other values should be either nil, or zero, depending on the type. There is though one important exception.

In this blog entry https://dave.cheney.net/2016/04/27/dont-just-check-errors-handle-them-gracefully, Dave Cheney, a prominent go developer, makes an in-depth introduction of the Sentinel Errors concept.

Some errors are meant to signal that processing cannot continue due to a problem with the current state.

Dave Cheney writes:

«The name descends from the practice in computer programming of using a specific value to signify that no further processing is possible. So to with Go, we use specific values to signify an error.»

The most well-known sentinel error is already mentioned in other answers io.EOF. There are some other places in the standard go library, where sentinel errors are used, e.g. archive/zip.

There is a big section devoted to your question, and to sentinel errors, in Learning Go by Joe Bodner (pp.161—165), which I recommend in any case, not only for a detailed answer to your question.

Upvotes: 4

phonaputer
phonaputer

Reputation: 1530

If you have decided that your function has completely failed at what it is trying to do, it is a good practice to return zero values for all non-error return values. This will avoid confusion or runtime errors (as much as possible) for anyone who chooses to call your function and forgets to check for errors. If your function has only produced junk results, it's better for callers to fail fast then to continue until a runtime error occurs at some indeterminate point in the future.

If your function has partially failed, but you feel that it has still produced some usable results, you can return a specific error type and document this case in the function comment.

Given the above, I would say you can do one of three things:

Return the successful result and also a specific error (which you document in the function comment). The caller can check for this error first, like so:

res, err := myLib.MakeHTTPCall()
if err == myLib.RequestBodyCloseErr {
   // do what you need to in case of this error
} else if err != nil {
   // general error handling
}

A second option is to return "" (the zero value for string) and the error from resp.Body.Close. In this case you are choosing to throw away the work the function has done up to this point.

The final option, if you don't want to stop processing for the error and you don't care enough about it to return it to the caller (I'm assuming this code is for you and your team and not some public library - if it's for a library the first two options would be better IMO.) is to simply log the error and return the successful result and nil.

Which option you choose is going to depend on how serious you think this error is. It also depends on how likely you think it is that this error will actually happen in production.

Upvotes: 2

Burak Serdar
Burak Serdar

Reputation: 51542

Although the general pattern for this is that if you have non-nil error the return value is undefined, there are well-known examples on the contrary, for instance, Reader.Read, which returns both a valid number and io.EOF in case of an EOF.

On the other extreme, gRPC libraries will fail if you both return a non-nil value and error at the same time.

So, this is to be decided case-by-case.

Upvotes: 10

Related Questions