go: Proposal: introduce "throws" keyword for error handling

First of all, I prefer leaving if err != nil alone, it’s quite good. But if we must add something for error handling, here’s a new approach that behaves like try() but can handle more scenarios, seems to be clearer, and most importantly, keeps compatibility with if err != nil ways.

The proposal suggests to add a new keyword throws for error handling.

The signature is simple:

// return the original error
func testFuncA() error {
    result, err := fooFunc() throws err
}

or

// return a new wrapped error with contexts
func testFuncB() error {
    message: = "error message"
    detail := "error detail"
    code := 500
    result, err := fooFunc() throws NewBarError(message, detail, code, err)
}

// or construct a BarError literal directly
func testFuncB() error {
    result, err := fooFunc() throws &BarError{
                                    Message: "error message",
                                    Detail: err.Error(),
                                    Code: 500,
    }
}

where NewBarError returns a new error of type BarError which implements the standard error interface.

In both cases, the last return value of enclosing function must be error interface type. if err != nil, the expression following throws will take place and the enclosing function returns with the expression evaluation result as the value of its last return parameter(error type) and its other return values are the type default values or the value of current corresponding variable(for named return parameters) .

If err == nil, the expression after throws will not execute and the program goes on.

Last, the throws keyword is totally optional for error handling, you can omit it somewhere and use it at another place in the same enclosing function.

Examples:

  1. The CopyFile example from the overview becomes:
func CopyFile(src, dst string) (err error) {
        defer func() {
                if err != nil {
                        err = fmt.Errorf("copy %s %s: %v", src, dst, err)
                }
        }()

        r, err := os.Open(src) throws err
        defer r.Close()

        w, err := os.Create(dst) throws err
        defer func() {
                w.Close()
                if err != nil {
                        os.Remove(dst) 
                }
        }()

        err = io.Copy(w, r) throws err
        err = w.Close() throws err
        return nil
}
  1. An example of returning custom error(an echo middleware example):
package main

import (
	"fmt"
	"net/http"

	"github.com/labstack/echo/v4"
)

const (
	successCode                   = 200
	serverErrorCode               = 500
)

// IngressError is the return error of manipulating ingresses.
type IngressError struct {
	httpCode int
	Code     int    `json:"code"`
	Message  string `json:"message"`
	Detail   string `json:"detail"`
}

// Error returns the formatted string of an IngressError.
func (e *IngressError) Error() string {
	return fmt.Sprintf("httpCode=%d Message=%s Detail=%s", e.httpCode, e.Message, e.Detail)
}

// NewIngressError returns a new IngressError.
func NewIngressError(httpCode, code int, message, detail string) *IngressError {
	return &IngressError{
		httpCode: httpCode,
		Code:     code,
		Message:  message,
		Detail:   detail,
	}
}


// CreateIngress creates an ingress
// POST /ingress
func CreateIngress(next echo.HandlerFunc) echo.HandlerFunc {
	return func(c echo.Context) error {
		spec := c.Get("spec").(*types.Spec)
		namespace := spec.Namespace
		name := spec.Name
		client := ingress.NewClient(namespace)
		message := fmt.Sprintf("create ingress error: %s/%s", namespace, name)
		result, err := client.CreateIngress(name, spec) throws NewIngressError(http.StatusInternalServerError, serverErrorCode, message, err.Error())

		c.Set("data", result)
		return next(c)
	}
}

I believe this approach may handle more scenarios than the try built-in proposal, especially when you need to wrap a new error for different function calls.

Drawbacks:

  1. introduce a new keyword.
  2. the code following throws may make the line longer(it depends).

About this issue

  • Original URL
  • State: closed
  • Created 5 years ago
  • Reactions: 34
  • Comments: 18 (5 by maintainers)

Most upvoted comments

@sirkon

I hope to be rational, not polite.

At least in this community, please be both rational and polite. Thanks.

@sirkon Please stay polite in discussions on this issue tracker. Please see Gopher Code of Conduct at https://golang.org/conduct. Thanks.

@sirkon Please stay polite in discussions on this issue tracker. Please see Gopher Code of Conduct at https://golang.org/conduct. Thanks.

@ianlancetaylor

I hope to be rational, not polite. This recent “try” proposal allows

info := try(try(os.Open(fileName)).Stat())

I can easily imagine me, senior, making such a mistake when I’m tired. And I bet juniors and even middles will do this all the time.

Ah, another one…

Downvoted, but I actually think this one is much better than retarded try

defer is Go’s RAII. I am not smart enough to see something descriptive and working at that level short of destructors from c++.

@sirkon – so the original “try” does Close(), or it leaks too?

info := try(try(os.Open(fileName)).Stat())

It leaks too. We don’t have a RAII in Go, so this essentially means a leak. That’s why try proposal is evil. Go type system is far from being able to handle this. This proposal gives nothing meanwhile: less lines but denser code.