Live data from Hacker News

Gopher Wrangling: Effective error handling in Go

stephenn.com

81–90 of 310 posts

Re: Gopher Wrangling: Effective error handling in Go

#81
post #47

Earlier quoted context omitted.

The Rust code you have isn't idiomatic. It's gar more likely to see: let greeting_file = File::open("hello.txt")?l vs file, err := os.Open("hello.txt") if err != nil { return fmt.errorf("faild to open hello; %w, err) } However, I would argue the distinction isn't just cosmetic. The compiler prevents me from not checking the error and blowing up the application with a nil pointer exception. The TFA even capes this pat…

Not a Rust programmer, but I took the Rust code from the Rust book. Admittedly I didn’t read the whole chapter, perhaps they talk idioms later.

You just needed to go down a bit further [0]. They understandably didn't want to introduce syntactic sugar until after they'd introduced the syntax it desugars into.

[0] https://doc.rust-lang.org/book/ch09-02-recoverable-errors-wi...

Re: Gopher Wrangling: Effective error handling in Go

#82

The provided examples highlight exactly why error handling in golang is verbose, error prone, and lacks context. Do people really not care about stack traces?

Less than I thought I would. I work with a very large Go codebase, and I don't remember the last time I had problems because I needed a stack trace. Just grepping for the error message is enough to show me exactly where it happened.

Still, this doesn't mean that Go does not have stack traces. It does have stack traces for panics, and you can create stack traces by wrapping errors.

Re: Gopher Wrangling: Effective error handling in Go

#83
post #76

Earlier quoted context omitted.

[flagged]

So what is the point of the distinction then? The examples I gave were idomatic Rust too. Both Go and Rust hand the caller an error and the caller then must do something with the error. In both Go and Rust, you can assign the error to underscore and ignore it. Java is different but we aren't talking about Java right now. The only difference is that in Go the function can (and must) still provide a value for "file" ev…

> Both Go and Rust hand the caller an error and the caller then must do something with the error.

This is simply not true. Go preaches that values should always be useful. Given a return signature (T, error), the value of type T can be used irrespective of what is contained in the value of type error. They are independent states.

> The only difference is that Go provides a value for "file" when there is an error.

Yes, that is ultimately the difference. Result takes what are independent states and tries to make them dependent. Result assumes that if there is a value of type error then the caller would not make use of the value of type T.

Often they will be dependent for all practical purposes. It is not an unreasonable assumption to assume that they are, especially if you know the caller. However, Go believes you cannot make that assumption. That you have to let the caller determine what it finds useful, not what you think it might find useful. Ease of code reusability is clearly a design goal of Go, so the idea that you can't get to know your callers and how they will use your function isn't unwarranted.

Tradeoffs, as always.

Re: Gopher Wrangling: Effective error handling in Go

#84
post #69

Earlier quoted context omitted.

This doesn't really make much sense. The producer knows what it's returning. In the _vastly_ common case, it's either returning an error or a success object, but the Go type system is unable to represent that. The caller trying to pretend that the success object is there isn't a freedom the caller gets in the current system, it's an artifact of the type system not being powerful enough to encode the situation accurat…

> The producer knows what it's returning. But doesn't know how the return values will be used by the caller. What is perhaps lost in this is where Go says that values should always be useful? > If you have a function that can return both an object and an error, there still should be a way to represent that (exactly the current way). Exactly the current way is what is said to be deficient, though. A function of this t…

This tries to sound profound but it really just misunderstands basic sum types. In Rust/Haskell/etc you're free to return multiple values if that's even potentially useful. If you have a function that can completely fail, partially fail (in this example, opening a file but then something else fails), or entirely succeed, you can encode that in the type system. In Go you cannot define this in the type system, you just have comments explaining how it works and hoping both that you implement it correctly and callers of your code read and understand them. If your code does return both files and errors in some cases, chances are callers are going to handle it incorrectly, especially if, say, the caller is responsible for closing files/responses/streams/etc.

In Rust/Haskell I can write a type with three values like Success(file), PartialSuccess(file, error), or Failure(error). Callers must then handle all of these. In Go I always have four cases for a simple function including those three and the final case of neither file nor error. Most Go callers will not handle the case where err != nil and file != nil and often the case where err == nil and file == nil will cause a panic and crash the program.

There was a tradeoff for this, but in this case the tradeoff was entirely in making the Go compiler simpler at the cost of making the Go language weaker and Go code more error-prone.

Re: Gopher Wrangling: Effective error handling in Go

#85
post #26

For the love of all that is good in the world, this is a solved problem, I don't understand why languages like Go, Kotlin, Python etc etc etc continue to insist on not having sane Option, Either, Try etc types.

> this is a solved problem, I don't understand why languages (...) insist on not having sane Option, Either, Try etc types.

This is not a "problem" as much as a conscious philosophical stance:

Errors don't actually exist, only conditions that you dislike. All the error handling you need is if/else. Everything else is unnecessary emotional baggage on some conditions that should not pollute your language. And even less so, gasp, your types (!).

Re: Gopher Wrangling: Effective error handling in Go

#86
post #76

Earlier quoted context omitted.

So what is the point of the distinction then? The examples I gave were idomatic Rust too. Both Go and Rust hand the caller an error and the caller then must do something with the error. In both Go and Rust, you can assign the error to underscore and ignore it. Java is different but we aren't talking about Java right now. The only difference is that in Go the function can (and must) still provide a value for "file" ev…

> Both Go and Rust hand the caller an error and the caller then must do something with the error. This is simply not true. Go preaches that values should always be useful. Given a return signature (T, error), the value of type T can be used irrespective of what is contained in the value of type error. They are independent states. > The only difference is that Go provides a value for "file" when there is an error. Yes…

Result covers the very common case where there is T or error but never both. Having a decent type system doesn't prevent you from returning both. In Go I simply cannot have something as simple as "A or B, but never both or neither" as a type.

>However, Go believes you cannot make that assumption.

This is trying to twist a weakness of Go's type system as a virtue. In Go I always have "A or B, including neither and both". I can have that in Rust or Haskell if I want, but usually I want "A or B, never both or neither" and rarely "A or B, sometimes both but never neither" which Rust and Haskell can do but Go cannot. This comes from Go using product types as a poor replacement for sum types.

Re: Gopher Wrangling: Effective error handling in Go

#87
post #26

For the love of all that is good in the world, this is a solved problem, I don't understand why languages like Go, Kotlin, Python etc etc etc continue to insist on not having sane Option, Either, Try etc types.

It was only in recent years that Rust has proven that monadic error-handling can be accepted in a mainstream language. At least, I hope it convinced enough people.

The more generic approach to error handling, using do monads (in Haskell and Scala) require some sort of do-notation (Scala's "for comprehensions") to be convenient. And I think this is a step that most mainstream languages are still too afraid to take. I would personally be glad for mainstream and some sort of monadic comprehension to become a mainstream language feature the same way closures became, but this is far from the reality.

So we are left with special-case solutions for specific problems like error-handling, iteration and nullability. Kotlin made it very easy do deal with nulls without a much ceremony (this is slightly more troublesome in Rust or Scala, for instance), while Rust chose to make error handling easier. Of course, they both repurposed the same operator ("?") for this purpose.

What Kotlin does with nullability and what Rust does with error-handling are both becoming quite palatable for mainstream language developers, but it's quite late to change language which have used exceptions (like Kotlin, Java and Python) or error values (like Go) to use monads right now. Entire APIs are built on the existing (and insufficient) error handling scheme.

For instance, we're using Arrow's Either on most new projects at work, but still have to deal with a lot of existing Java APIs, which are exception-based.

Re: Gopher Wrangling: Effective error handling in Go

#88
post #2

Always wrapping errors can be a good way to get a stack trace of the error path in the logs.

Is that a real stack trace, or just a trace of error message wrapping? I haven’t figured out how to extract a real error message using Go stdlib

No it’s a manual “stack” you build yourself with wrap. It’ll take you to the nearest error handler to the error which is usually not that far from the real problem

Re: Gopher Wrangling: Effective error handling in Go

#89
post #26

For the love of all that is good in the world, this is a solved problem, I don't understand why languages like Go, Kotlin, Python etc etc etc continue to insist on not having sane Option, Either, Try etc types.

Yeah this is a solved problem... with exceptions

Re: Gopher Wrangling: Effective error handling in Go

#90
post #86

Earlier quoted context omitted.

> Both Go and Rust hand the caller an error and the caller then must do something with the error. This is simply not true. Go preaches that values should always be useful. Given a return signature (T, error), the value of type T can be used irrespective of what is contained in the value of type error. They are independent states. > The only difference is that Go provides a value for "file" when there is an error. Yes…

Result covers the very common case where there is T or error but never both. Having a decent type system doesn't prevent you from returning both. In Go I simply cannot have something as simple as "A or B, but never both or neither" as a type. >However, Go believes you cannot make that assumption. This is trying to twist a weakness of Go's type system as a virtue. In Go I always have "A or B, including neither and bot…

> This is trying to twist a weakness of Go's type system as a virtue.

No. There is no discussion about type systems taking place here at all. The discussion is about patterns where the producer or the consumer is in control. Specifically, Result puts the producer in control. Idiomatic Go (T, error) sees the consumer in control.

There is likely no language in existence that prevents you from choosing. You can write code where the producer is in control in Go, and you can write code where the consumer is in control in every other. These are not features of a language, although language idioms do often push developers one way or the other.

Post reply on HN