Live data from Hacker News

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

github.com

311–320 of 425 posts

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

#311

Earlier quoted context omitted.

I guess then I have to ask, why would try() make that worse? Because I can't stand Golang error handling. It's repetitive, it's error prone, and other language features interact with it so that when you make a mistake it can be as hard as a double free to track down where the erroneous default value was introduced. On the other hand, using Rust, Ocaml, F# or Haskell I understand how my code composed and I can be conf…

Try makes it worse because it is so easy to miss when reading the code. Because it encourages nesting function calls, which is harder for a human to parse than separate statements across lines. Because it means you can exit the current function from the middle of a line of code, and what runs before or doesn't run before is based on order of operations rather than requiring the exit to be a statement on its own line…

Writing Go professionally for 4 years already and being a Go fanboy since 2009: while endorsing many benefits of "if err" blocks, I do very much have the following issues with them (in no specific order):

- It's hard to spot outliers. This leads to occasional bugs that tend to get easily overlooked in code review. Also, it makes code reading harder when an "if err" block is subtly different. The most common case here being "if err == nil" (sometimes bug, sometimes on purpose) - super hard to notice.

- You say you have seen missed "if err" blocks only a few times. I say that's 100% too many; every one of them in my experience was a subtle bug (possibly comparable to off-by-one errors in C).

- When I need to focus on understanding/analyzing the optimistic path in a fragment of code (always the first thing I do when reading), the everpresent "if err" blocks introduce tiresome visual noise and make the reading/grokking process slower and harder (having to constantly try and mentally filter out some 80% of what my eyes see).

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

#312

Earlier quoted context omitted.

Why is a monad the “best” solution? Best according to what criteria? Special purpose syntax can buy you a lot more. For example Swift’s try makes it obvious which statements contain error handling without burdening each expression.

Ok let me tell you the criteria. There are two. First: The monad allows for composition of functions. Returning two values does not. It breaks the flow of a function pipeline and forces you to handle every error in the same way. Second: Extracting the value via pattern matching guarantees that the error will either be handled or used correctly. This is a way to 100% guarantee that there are No runtime errors. That's…

I'm interested in the zero runtime errors piece - how would a language with the maybe monad handle an out-of-memory error at runtime?

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

#313

Earlier quoted context omitted.

One foundational principle of Go is that the sad path is at least as important, and maybe more important, than the happy path. The best Go programmers I know write the sad path of their programs first, and then backfill the happy-path logic. So: > you only need to write [error checking] when you actually have something meaningful to do. Although it's the subject of a lot of ridicule, `if err != nil { return err }` is…

The fact that you need to add context to errors usually exacerbates the problem and makes it even harder to read the code. You often end up with err = doThing() if err! = nil { return errors.New("Error doing thing", err) } This doesn't add any useful information for whoever is reading the code, it's just boilerplate that you learn to skip while reviewing, while hopefully not missing any important thing that does happ…

First, the signature for `errors.New` is `New(text string) error`. It won't take more parameters than that. So I guess you mean `fmt.Errorf`.

If above is true, then how about:

    err := renderTemplate()

    if err! = nil {
        return fmt.Errorf("Error rendering template: %s", err)
    }
The end error could then be something for example:

    Error rendering template: Compiler has failed: Cannot load template: File /tmp/test.tpl was not found
    ------------------------  -------------------  --------------------  --------------------------------
     |                         |                    |                     |
    Returned by that           |                    |                     |
    example                   Returned by the       |                     |
                              fictional compiler   Returned by the        |
                                                   fictional template    Returned by the fictional file 
                                                   Loader                reader
I didn't even twist your example, and yet you can already see more information. And with that information, even a user can understand what's going on clearly. So ... more useful?

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

#314
post #129

Earlier quoted context omitted.

>There isn't a meaningful difference in cumbersomeness between having two try-catch blocks and two if-err blocks. There is a meaningful difference in cumbersomeness between what Go has today and "try foo(try bar())". Which is why it's so unfortunate that the community killed the try proposal. Object thing1; try { thing1 = doStuff(); } catch(SameException e) { // handle error 1 } try { return doOtherStuff(thing1); } c…

How often do you actually need to handle those errors differently? In my experience, it is vastly more likely that a function which can throw errors in Java looks like this: Stuff foo() throws SameException { return doOtherStuff(doStuff()) } Whereas in Go the exact same function must be written like this: func foo() (Stuff, SameException) { thing1, err := doStuff() if err != nil { return err } thing2, err := doOtherS…

FWIW when debugging i prefer the second style if for no other reason than that i can place a breakpoint in doOtherStuff while skipping doStuff. Also reading it, it is more obvious that the code calls both doStuff and doOtherStuff (though with just two calls it isn't a big different, imagine having a 2-3 more calls in there).

(also why debuggers still insist on line-based breakpoints is beyond me, why can't i right click at a call and put breakpoint at the call itself instead of the line where the call lies on?)

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

#315
post #81

Earlier quoted context omitted.

> My guess is that gofmt is a huge factor in this It really is. My code looks like your code and the next person's code. Go is opinionated and strict and that makes reading other people's code so much easier

Experts' code should be clearer and more concise than novices' code. If that's not true, there's no payoff for getting more proficient with the language, and it's not doing enough to help you.

golang is not primarily designed for experts

"The key point here is our programmers are Googlers, they’re not researchers. They’re typically, fairly young, fresh out of school, probably learned Java, maybe learned C or C++, probably learned Python. They’re not capable of understanding a brilliant language but we want to use them to build good software. So, the language that we give them has to be easy for them to understand and easy to adopt." – Rob Pike

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

#316
post #313

Earlier quoted context omitted.

The fact that you need to add context to errors usually exacerbates the problem and makes it even harder to read the code. You often end up with err = doThing() if err! = nil { return errors.New("Error doing thing", err) } This doesn't add any useful information for whoever is reading the code, it's just boilerplate that you learn to skip while reviewing, while hopefully not missing any important thing that does happ…

First, the signature for `errors.New` is `New(text string) error`. It won't take more parameters than that. So I guess you mean `fmt.Errorf`. If above is true, then how about: err := renderTemplate() if err! = nil { return fmt.Errorf("Error rendering template: %s", err) } The end error could then be something for example: Error rendering template: Compiler has failed: Cannot load template: File /tmp/test.tpl was not…

Oops, I forgot if errors.New takes the 'cause' as well.

Regarding you example: the code itself still contains redundant information for someone reading it. True, the error ends up being nicer, though I would argue that the user would have been better served with a simple 'failed to load template file: /tmp/test.tpl', no need to show the pseudo call stack (so, only the fictional template loader should have been wrapping the error,for this particular case). And for a developer, the full call stack may be more useful. Exceptions would give you both for free - a nice message that can be shared to the user by whoever caused the most understandable error, and a call stack that can be logged at the upper layer so developers can see it if a bug is logged, and get a much fuller context.

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

#317

Earlier quoted context omitted.

I thought it could lead to doing method chaining for a fluent like API which I find cleaner than how things work now.

That can easily be done right now with the current way of error handling.

Do you have any examples?

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

#318
post #289

Earlier quoted context omitted.

One foundational principle of Go is that the sad path is at least as important, and maybe more important, than the happy path. The best Go programmers I know write the sad path of their programs first, and then backfill the happy-path logic. So: > you only need to write [error checking] when you actually have something meaningful to do. Although it's the subject of a lot of ridicule, `if err != nil { return err }` is…

> Errors in Go are, at a minimum, annotated with contextual information before being returned. What surprised me when I last wrote Go was that there was no out-of-the-box solution to adding a stack trace to the error.

Does anyone know if this was a conscious decision? I mean IIRC in Java you're generally discouraged from throwing errors for control flow because creating the stack trace is a relatively heavy process. In Go this is of less concern and returning an error is pretty normal for control flow (as in errors are expected, not exceptional), and you shouldn't have to worry that an error path would be 100x as expensive as a normal flow because a stack trace is being generated.

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

#319

Earlier quoted context omitted.

The difference with catch blocks in Java, C++ or Python is that you only need to write them when you actually have something g meaningful to do. If you only need to propagate the error or cleanup resources then propagate the error, then all you would write is... Nothing. And cleanup+propagation is by far the most common error handling strategy. In Java and Python exceptions even add context for you automatically to h…

But the issue with this is that it can be hard to know what all the possible error conditions are, and thus whether you have anything meaningful to do. Using Rust, which makes errors explicit like Go, has been eye-opening to me. My programs never crash because I've handled every error condition. No effort on my part. No tests needed.

Java also makes you handle every possible error condition, unless of course you chose to use an escape hatch. Rust allows the same.

By the way, Go is much happier to crash than Java - for example, a simple array index out of range will cause a program crash in a typical Go program, where it would only cause a request failure in a typical Java program. Not sure how Rust handles this.

Finally, choose that isn't tested (manually or automatically) is very unlikely to work. Maybe you can guarantee it doesn't crash, which is a much weaker guarantee, but I doubt even fully proven code (like seL4) is all bug-free before ever being run.

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

#320
post #314

Earlier quoted context omitted.

How often do you actually need to handle those errors differently? In my experience, it is vastly more likely that a function which can throw errors in Java looks like this: Stuff foo() throws SameException { return doOtherStuff(doStuff()) } Whereas in Go the exact same function must be written like this: func foo() (Stuff, SameException) { thing1, err := doStuff() if err != nil { return err } thing2, err := doOtherS…

FWIW when debugging i prefer the second style if for no other reason than that i can place a breakpoint in doOtherStuff while skipping doStuff. Also reading it, it is more obvious that the code calls both doStuff and doOtherStuff (though with just two calls it isn't a big different, imagine having a 2-3 more calls in there). (also why debuggers still insist on line-based breakpoints is beyond me, why can't i right cl…

You could also write the first function body as

  Stuff thing1 = doStuff()
  return doOtherStuff(thung1)
It's still easier to read without the explicit error handling.
Post reply on HN