Skip to content

Instantly share code, notes, and snippets.

@aarzilli
Last active September 6, 2018 04:14
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save aarzilli/1a85db632edecc8159505e2c785882ed to your computer and use it in GitHub Desktop.
Save aarzilli/1a85db632edecc8159505e2c785882ed to your computer and use it in GitHub Desktop.
Feedback for Go2 error handling design

Check as an operator

I don't see why check should work like a unary operator and have its own special syntax rule instead of acting like any function call.

Go doesn't have anything like the proposed check, it has unary operators but they are all symbolic (for example !, unary * and &). It would be the first thing in the go language to look like C's sizeof.

Adding this new syntax makes parsing old code either impossible or context sensitive, for example:

check +a 

Does this parse as check(+(a)) or as (check)+(a)? In Go1 it's the latter in Go2 it would have to be the former, unless the parser first checks whether there is a check identifier defined.

On the other hand there is plenty of precedent for special builtins in Go: make and new both look like ordinary function calls even though they could never be defined as such.

I argue that check should be like make or new, not like sizeof.

Handler chains

I don't see any justification for why handler chains are necessary. IMHO requiring every handle block to terminate with a return is completely fine.

I can see how a handler chain would be useful in some circumstance but those are definitely a small minority and even in those cases I think the handler chain will make writing the code marginally easier at the expense of making reading the code harder.

Go has always been about sacrificing syntactic sugar in favor of readability. I would prefer to see no handler chains in Go2.

Going through the examples in the draft document:

func process(user string, files chan string) (n int, err error) {
    handle err { return 0, fmt.Errorf("process: %v", err)  }      // handler A
    for i := 0; i < 3; i++ {
        handle err { err = fmt.Errorf("attempt %d: %v", i, err) } // handler B
        handle err { err = moreWrapping(err) }                    // handler C

        check do(something())  // check 1: handler chain C, B, A
    }
    check do(somethingElse())  // check 2: handler chain A
}

Turns into this without handler chains, which is clearer:

func process(user string, files chan string) (n int, err error) {
    handle err { return 0, fmt.Errorf("process: %v", err)  }      // handler A
    for i := 0; i < 3; i++ {
        handle err { return 0, fmt.Errorf("process: attempt %d: %v", i, moreWrapping(err)) } // handler B

        check do(something())  // check 1: handler B
    }
    check do(somethingElse())  // check 2: handler A
}

The SortContent example already uses the single handler style and would be unchanged.

The ProcessFiles example, turns into this:

type Error struct {
	Func string
	User string
	Path string
	Err  error
}

func (e *Error) Error() string

func ProcessFiles(user string, files chan string) error {
	e := Error{ Func: "ProcessFile", User: user}
	handle err { e.Err = err; return &e } // handler A
	u := check OpenUserInfo(user)         // check 1
	defer u.Close()
	for file := range files {
		handle err { 
			e.Err = err
			e.Path = file
			return &e
		}
		check process(check os.Open(file)) // check 2
	}
	...
}

Which is slightly more verbose but I think clearer.

In cases where code sharing between handlers in a chain is substantial I'd rather see it factored out into a helper function.

@gmichelo
Copy link

gmichelo commented Sep 2, 2018

I agree with @networkimprov. I believe that check() can be seen as a traditional function call, which is not in fact. This could be confusing. I would rather prefer to keep it as an explicit operator, so that it is immediately clear that you are doing an error check.

@Duanqy, I think what you are suggesting goes against the principle of having error checking explicit. For the sake of debugging, it is much better to know where the error can come from.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment