Live data from Hacker News

Proposal: A built-in Go error check function, try

github.com

41–50 of 127 posts

Re: Proposal: A built-in Go error check function, try

#41
post #28

Earlier quoted context omitted.

I've spent the last year working on a Go system, writing Go every day. After a couple of months, I just stopped seeing/worrying about "if err != nil {". It has become punctuation, the Go equivalent of semicolons; I don't even use snippets; I manually type that every time. Which is good, because it does make me think about whether this function call can error, and what I should do about it if it does. 90% of the time…

I used to write code like that during the 80 and 90's, until settling down in languages with first class support for exceptions. So yeah, I did it for around 20 years, and don't miss it.

I don’t like exceptions at all. They do not make it obvious what is going on, I never am completely sure if I’m handling them right. When I want to throw an exception I’m often unsure which would be right, and sometimes your API can have multiple reasons to throw the same exception.

Go error handling is not like that. But I can sure as hell say fairly that Go error handling is likely also not similar to what you did for 20 years in the 80s and 90s either. Rob Pike and friends surely knew a lot about what programming was like at that time. I, being relatively a youngster, don’t first hand, but I can tell you my experiences with C++, PHP, Python have not been nearly as good as Go with error handling.

For one thing, C++ has no rigid standard for how to handle errors. Some people use exceptions, some used error methods on classes (including the standard library,) some used special integer or enumeration values (...including the standard library,) and some had libraries and frameworks have their own magic error handling mechanisms. This cognitive overhead was horrible. C wasn’t much better; atoi is a case study in why error handling in C sucks. Libraries that tried to standardize it, like SDL, were bearable if it was all you used, but it probably wasn’t, and some APIs, like Win32, made it even worse. (And I suppose it is worth at least mentioning setjmp/longjmp error handling. I don’t think it’s necessary to comment on why it’s not good.)

Python exception handling is admittedly better, but its not really wonderful. Exception handling code in Python is prone to breakage that is first detected at runtime. If a function implementation changes, and the set of exceptions it might throw changes, that’s an invisible API change that may cause an unhandled exception in production. Not so great. Also, on a vaguely related note, you can’t really do error values using multiple returns like in Go, because Python doesn’t support multiple returns, only tuples, and refactoring between returning values and tuples is likely to run into accidental runtime errors (though you can paper over this issue a bit with type checking.)

PHP error handling sucks, I will withhold from elaborating.

All of the exception handling mechanisms suffer from one problem I really don’t like: it’s another nearly invisible part of the API. It makes the wrong thing (not handling errors) easy, and the right things (handling the appropriate errors correctly) hard. Your dependencies have to care about your call tree, and if it changes in refactoring it’s anyones bet what kinds of exceptions your function might throw. You could catch all exceptions, but because language errors like syntax errors (JavaScript) and index out of range and property name errors (Python) can also be exceptions, you rarely want to catch all exceptions. Not to mention, your call itself would be caught, so any exceptions caused by anything else in the try block would also be conflated.

Go does some things that are mostly not new, but haven’t all been packaged together this way before exactly:

- Custom errors via implicit interfaces, allowing easy, arbitrary data to be passed through errors while maintaining full control of error messages

- Deep separation of programming errors and operational errors tend to be passed as error values. Programming errors, like indices being out of bound or misusing an API, typically results in a panic, whereas operational errors. Very seldom do you actually care what the error is, but when you do you can inspect the error as any other value, because it is just any other value.

- Ecosystem-wide standards for how to pass errors. It’s almost 100% universal that errors are passed at the end of the return list. This makes it easy to parse for humans and easy to lint for machines. Linters can warn you about unused error values, and if a function suddenly has an error return it’s an API break, forcing you to fix existing code to properly handle errors.

- Good library support for error types, including fmt.Errorf for one-off errors that don’t need special handling, and (third party) a myriad of error wrapping/helper libraries. They’re not needed at all, but can be quite handy.

It works a lot better imo. You have more ceremony but less guessing. You can read a function and see almost every edge case, and when all of the functions perform good error hygiene you no longer need to guess about what refactoring your code will do.

I’ll take my repetition.

Re: Proposal: A built-in Go error check function, try

#42
post #10

I get that the error is still propagated "manually" behind the scene; but how is this different from exceptions in practice once you use try everywhere (except where you forgot and the error is dropped silently)? Here is my proposal: add restarts [0] as a complement to manual propagation. [0] https://github.com/codr7/g-fu/blob/master/v1/doc/typical_res...

Because it doesn’t skip the call chain - each function must explicitly handle the error or pass it on. In practice usually errors are handled one or two levels up, not with some global error handler as people do with exceptions. Also the error is in the function signature, unlike exceptions.

Also, you don’t use try everywhere, that’s the point. They could do with some better examples.

Re: Proposal: A built-in Go error check function, try

#44

Please for the love of god no. Go is awesome for its simplicity. Errors should not be abstracted out of handling convenience. Errors are just values either eliminate the need for the error or handle it like you would any other value. Stop trying to make go work like every other language.

This isn’t a replacement for go error handling, it’s a complement. Sometimes you don’t need to do anything other than pass an error up the chain.

Re: Proposal: A built-in Go error check function, try

#45
post #6

Earlier quoted context omitted.

After chewing on this for a while, I've come to the conclusion that the thing exceptions does wrong (or if that is too controversial, substitute "most dangerously") is that it disconnects handling the error from the scope that generated it. It's the way exceptions so easily fly up the stack into code that can't understand them because it is too distant in context that is the problem. Neither this proposal, nor any ot…

Every time I switch back to exception languages, I get this tendency to "assume everything succeeds all the time, and handle it at the very top level in case any part of it fails". I do not think about what can go wrong at each level nearly as much as I do when I am required to use `if err != nil` soup.

> Every time I switch back to exception languages, I get this tendency to "assume everything succeeds all the time, and handle it at the very top level in case any part of it fails"

See, I get the exact opposite tendency. Exceptions mean that any statement, no matter how seemingly trivial from the caller's perspective, can fail. It means I'm constantly thinking "If an exception is thrown between X() and Y(), does it break any assumptions I've made about the program state?". You end up with hacks like putting code in finally blocks just to prevent partial execution.

I think exceptions would work fine in a language that also had built-in support for transactional memory, where you could commit or rollback operations when an exception is thrown partway through. Without any language support, though, I think that exceptions and mutable state do not play nicely with one another.

Re: Proposal: A built-in Go error check function, try

#46
post #4

So basically they're proposing: f, err := os.Open(filename) if err != nil { return …, err // zero values for other results, if any } can be simplified to f := try(os.Open(filename)) This makes a lot of sense, but I'm of two minds. On one hand, it makes things much cleaner. On the other hand, it might be a first step onto a slippery slope that ends with exceptions. A lot of others chiming in with different ideas on th…

> it might be a first step onto a slippery slope that ends with exceptions.

Like that “slippery slope” with code generators leading to generics?

To me it seems like golang-users actually wants all those features it’s language-designers took away.

Re: Proposal: A built-in Go error check function, try

#47
I suspect the downside is that it'll promote blind propagation of errors.

In rust that's fine because the type system will document what error types can be returned.

In golang, it important that every error type that can be returned is manually documented. Otherwise, it's better to just panic, since nobody can handle unknown errors anyways..

Or am I missing something?

Re: Proposal: A built-in Go error check function, try

#48
post #18

I am strongly against this. `try` seems exactly like a function yet it is not acting like a function at all. People wouldn't expecting calling a function may return from the caller. And there is a reason why golang doesn't have macros. With macros all kind of craziness would be possible, and would really difficult to read different kind of projects' code.

Agreed - I thought this looked pretty reasonable, if a bit parenthesis-heavy, until I saw this example:

    func printSum(a, b string) error {
        fmt.Println(
                "result:",
                try(strconv.Atoi(a)) + try(strconv.Atoi(b)),
        )
        return nil
    }
When you nest the calls to try inside another method call, like this, the control flow really becomes obscured.

Re: Proposal: A built-in Go error check function, try

#49
post #47

I suspect the downside is that it'll promote blind propagation of errors. In rust that's fine because the type system will document what error types can be returned. In golang, it important that every error type that can be returned is manually documented. Otherwise, it's better to just panic, since nobody can handle unknown errors anyways.. Or am I missing something?

Not really. You can just check if “err != nil” and switch control flow on this.

Re: Proposal: A built-in Go error check function, try

#50
post #4

So basically they're proposing: f, err := os.Open(filename) if err != nil { return …, err // zero values for other results, if any } can be simplified to f := try(os.Open(filename)) This makes a lot of sense, but I'm of two minds. On one hand, it makes things much cleaner. On the other hand, it might be a first step onto a slippery slope that ends with exceptions. A lot of others chiming in with different ideas on th…

> it might be a first step onto a slippery slope that ends with exceptions.

And what? Why avoid exceptions?

Post reply on HN