Hacker Timesnew | past | comments | ask | show | jobs | submitlogin

The whole point of error handling in go is that magic does not happen. Large error handling frameworks can create tracing difficulties. In the authors example he will never be confused as to where the error occurred. Is it overly verbose, yes. Do I prefer it yes. I am quickly able to identify and address issues.


well, no magic, goal reached.

However, the current implementation of error handling is atrocious.

If Go would take sum types and question marks from Rust, Go would keep the general error handling strategy and philosophy with the difference of being way less verbose and more ergonomic.


> If Go would take sum types and question mark

Go is an incomplete language masquerading as a simple one.


I get that you don’t like some of the features go has done without, but how do you justify incomplete? What do you even mean by that word? Clearly lots of code has been written in it. Its deficiencies do not prevent programs from being expressed. The only definition I can think of for complete would be Turing completeness, but that is clearly too low a bar for what you are saying.


> but how do you justify incomplete

are you seriously suggesting that Go's insistence of requiring the programmer to scatter "if err != nil {}" after most function calls - with no compiler assistance if you forget - is complete either in the sense of "having all necessary parts, elements, or steps" or "being finished"?

it's terrible, causes bugs and it seems incredibly unlikely to me that it won't be improved.


No "compiler assistance" doesn't mean no assistance at all. There is a growing set of linters based on the community-accepted analysis package [0] that prevent this and other gotchas that Go devs may be prone to.

It isn't a perfect solution, but it works at community level without needing to wait for the Go team to take care of or changing the language/toolchain.

0: https://pkg.go.dev/golang.org/x/tools/go/analysis


At very least, unless you hate other developers for some reason, you need to document in your test suite what errors are returned.

How, exactly, are going to forget a necessary `if err != nil` without your test suite blowing up?

If you do hate other developers for some reason, why not leave out some `err != nil`s to really drive home your hatred towards them?


If you can forget to check an error at a call site, then you can just as easily forget to add a proper test for that error condition. I prefer writing in languages where the syntax and standard library and compiler will help me avoid mistakes, and not have to rely on tests -- more error-prone code I have to write! -- to do that for me (and yes, I understand that I will never avoid having to write some tests, but the more the language and compiler can do for me, the better).

A language with generics and sum types (so you can implement something like Either, Try, or Result) doesn't let you use the result of a fallible function call without handling the error explicitly. And if the result is unit/void, most compilers/linters will still warn you if you haven't used the Either/Try/Result type that's returned.

But ultimately it doesn't matter. Write in whatever language you feel comfortable and most productive.

Well, unless you're starting a new security-sensitive project and want to write in C. Just... stop.


I don't follow. Errors are part of your functional specification. Not even a complete type system can avoid you needing to write tests to document the error conditions. We are not talking about writing tests to stand in where an incomplete type system may be lacking.

Assuming you don't hate other developers, and given the tests you would write in any language, how do you foresee missing an error branch? Again, not specifically trying to test for something that a sufficiently advanced type system would catch, just your regular level documenting that demonstrates to other developers that you don't hate them.

> then you can just as easily forget to add a proper test for that error condition

If an error condition is easily forgotten in the documentation, then who cares about the implementation thereof? It is obviously not important. In fact, it is arguable that your program is incorrect if you do handle the condition in such a case.


what tool do you use to verify that every possible path your program, including all errors paths in dependencies, is executed?


If you are referring to test coverage, the standard Go toolchain includes that out of the box.

But in this case you don't need to know about what paths have been executed. It will be obvious that an error path was not handled when your program does not adhere to function that is documented.


> including all errors paths in dependencies


Goland and Emacs (with Tree Sitter) can at least generate those automatically for you so they won't be forgotten, fwiw.


> if you forget

Does this happen? Is there any data on how often error checking is forgotten?


In my experience, outright forgetting is very rare because an unused err should be a warning, but the following is not nearly as rare as it should be:

if err != nil { return nil }

With how boilerplate the correct version is, you learn to skip over it entirely, meaning it can be hard to spot that anything is wrong here. Linters do exist for this now, but they have so many false positives on other error handling cases that most people don't bother with them.


I personally am not a fan of Go, but I agree that it's a fine language that people are productive in.

Opinions about languages are going to vary a lot, and I don't think it's productive to debate this kind of thing.

I prefer languages with stronger, more expressive type systems, but I'll freely admit that I code slower (though usually more correctly) in languages like that. Some people prefer languages where they can easily hold all the language's syntax and rules and quirks in their head, and fire out code quickly.

All of that is fine. There's no objective truth there. I agree with the GP that Go feels incomplete. Before generics, it felt embarrassingly incomplete. But if you're fine and happy and productive with it as it is, that's great!


I think it's more a sign that Go will eventually pick up (some of/all of) the features people complain of it missing, in time. Maybe when it's a complete language it will still be simple; maybe trying to keep it artificially simple now will hamper it later. Remains to be seen.


From “the rise of worse is better”:

> Completeness -- the design must cover as many important situations as is practical. All reasonably expected cases should be covered. Completeness can be sacrificed in favor of any other quality. In fact, completeness must be sacrificed whenever implementation simplicity is jeopardized. Consistency can be sacrificed to achieve completeness if simplicity is retained; especially worthless is consistency of interface.


Rust-the-language, maybe—but Rust-the-ecosystem makes heavy use of magic crates like `thiserror` and `anyhow`. I’m not saying it’s a better or worse philosophy, but it’s certainly a different philosophy.


thiserror and anyhow are hardly magic.

The former is just macros for error definition

The latter is just a wrapper around box dyn error


“Magic” isn’t a rigorously defined term, so let’s ignore it. Support for macros is a huge philosophical difference between Rust and Go.


I disagree in the context of error handling.

The go ecosystem frequently utilizes `go generate` to handle boilerplate code generation. That’s the exact same way `thiserror` utilizes macros to generate plain error structs. While macros can do more, in this case it’s the same.


thiserror and anyhow exist for ergonomic purposes (and enhance the experience of using the question-mark operator). Libraries shouldn't be leaking thiserror or anyhow types into their public interface. If they are, that's bad design.


While sum types would be a welcome addition in general, summing an error is logically erroneous. It is an independent state.

The question mark is interesting, but as the try proposal discovered, how do you solve the leaking problem? That doesn't have a good solution yet.


> Summing an error is logically erroneous. It is an independent state.

Doesn't compute for me. Care to explain?


In practice it’s either-or in “error handling”, because that’s when you decide what to do and not do next.


Not true. As zero values are to be useful, one should never have to observe the error state unless the error is significant to the caller for some reason. Sometimes the error is significant. Sometimes it isn't. That depends on what the caller's needs are. Summing the error makes assumptions about the caller that may or may not be true. That is a bad API design.


Yes this is why you can just operate on the non error value (with map), immediately propagate the error (with ?), just operate on the error value (with map_err), or ignore it completely (eg by converting it to an option or using something like unwrap_or_else). Errors in Rust are still just possibly present values: they’re just wrapped in an interface (a sum type) that forces you to be explicit about your intent to ignore, handle, or propagate the error.


Propagation suffers from the leaking problem. Rust has solutions for that, but it's not clear how that translates to Go, hence why the "try" proposal failed. I'm sure there is some solution out there, but nobody has come up with a good one. It turns out if nobody does the work, the work doesn't get done.

Again, needing to be explicit about your intent to do anything with an error is faulty. The values are not dependent. Just because some languages have gotten it wrong doesn't mean they all should.


What you're saying doesn't make sense to me, and I also don't know what you're referring to by the leaking problem. I don't see how the choice of being explicit or non-explicit about errors is "faulty." Plenty of languages choose to use exceptions and maximize your ability to ignore errors, while others force you to deal with (in some way) the fact that an operation is fallible. Neither of these is "wrong," in my opinion, but they do offer different tradeoffs and ergonomics.

I'm not sure why you say that the values of a sum type must be "dependent," or what you mean by that. Maybe/Option is a sum type of Nothing or Some<T>. There's no dependency there. If my webserver can return JSON or XML, I might represent that with a sum type holding either a JSON object or an XML tree, but there's no dependency between those two things that I can see.


What is the "leaking problem"?


It's not atrocious, when you work on Go code on a daily basis it's fine but I agree that it's repetitive.

Really an overblown problem from people that don't use Go much tbh. I work with people that used extensivly Java / C# / C++ and it's not the thing they complain about. They usually enjoy the fact that it's not exception based.


I really appreciate the lack of magic in go's error handling. I do not appreciate the specific implementation of errors in go. It's inefficient (`reflect` is all over the place) and prone to obscuring the actual "error path". In applications of non-trivial size, it also becomes an obstacle to implementing structured logging and other components of observability. This makes debugging/triage of, say, a production incident harder than it needs to be.


in 8 years of writing go at scale i've never used reflection in production code. gonna need an example of what you mean.


I've never seen or used reflection with error handling in Go, what do you mean?



I wouldn't classify type assertion as reflection. It's a super fast type check of the object header, not dynamic reflection.


But this is a detail of implementation, user code does not use reflection with errors.


That "detail of implementation" is what is used by user code, e.g., `myError.As(...)` uses reflection, implicitly.


Then what? It has 0 performance impact, we're talking about error handling here, it's rare and not in hot path.


It does not have 0 performance impact, it's not rare, and in applications that are in the hot path, error handling is also in the hot path.


No, it only executes when you enter the err != nil block so it's almost never called, again 0 impact on performance.




Guidelines | FAQ | Lists | API | Security | Legal | Apply to YC | Contact

Search: