How do I return an error all the way to the top level?

My Go code all looks like this, for almost every function call. That is a lot of boilerplate, three extra lines on each stack level just to generate an error code. Is there a cleaner way to do this?

function1 here is just a stand-in for any function. There can be many such, each calling another, nested dozens deep.

...

o, err := function1(x)
if err != nil {
    log.Println("Got an error", err)
    return MyStruct{}, err 
}

...

In Python, Java etc I would throw an exception, then handle it far up the stack. Intermediate stack levels would not need to have any code for that. I know that Go does not do exceptions that way, so what is the boilerplate-free solution?

I could use panic, but I know that that is not recommended.

I am not quite sure what you want to do. But you can use errors.Wrap to wrap err.

if err != nil {
    return MyStruct{}, errors.Wrap(err, "got an error")
}

What I want to do is throw an exception at the relevant point, and only worry about it at the top level, e.g. the HTTP handling level where I return an HTTP status code.

I know that Go does not do exceptions comparably to Python, Java etc, so I am looking for a solution that will not require boilerplate on every function call.

(I edited my question accordingly.)

There are no exceptions and no throwing. You will have to handle an error where it happens.

One way to handle it is to wrap it so the current level on the call stack can add additional information, and return it to the caller. See https://blog.golang.org/error-handling-and-go.

Thank you. Right, I know that Go does not have exceptions.

Handling an error where it happens often means returning it from the function; then the calling function again returns this error (or a wrapped error), and so on up the stack.

That seems like a lot of boilerplate for every function call. Is there a way to avoid such boilerplate?

None that I know of. This behavior is intentional. The designers of Go wants the programmer to handle errors where they occur so an error can’t be unintentionally be ignored. This happens all the time with unchecked exceptions in Java, for example.

Thank you. I understand that that is intentional design. I actually like ignoring errors, since many (not all) errors cannot be handled other than to return an error to the end-user.

The Go approach seems like a lot of boilerplate, but I guess I’ll get used to it :slight_smile:

If your API is small enough, panicking might be an option for you. You should never use a panic to return an error to consumers of your API, but if you don’t want to handle errors within your API, you can panic and then put a deferred recover at your API boundaries. For example:

func SomePublicFunction(arg0 int, arg1 float32) (res interface{}, err error) {
    defer func() {
        v := recover()
        if v == nil {
            return
        }
        if e, ok := v.(error); ok {
            err = e
            return
        }
        err = fmt.Errorf("%v", v)
    }
    return internalFunctionImpl(arg0, arg1)
}

func internalFunctionImpl(arg0 int, arg1 float32) interface{} {
    if arg0 == 0 {
        panic("arg0 cannot be 0")
    }
    return int(arg1 / float32(arg0))
}

The encoding/json package does something similar. If your API has more than a few public functions, then using panic and recover this way is clunkier than just passing errors around, but if it’s small, then wrapping your public functions this way might be worth it.

That makes sense. Most of my code is not an API, just internal functions. I think that the typical error, that cannot be meaningfully handled and just results in returning an HTTP error status or the like, should result in panic and then recovery and handling on the top level.

This topic was automatically closed 90 days after the last reply. New replies are no longer allowed.