Hacker News new | ask | show | jobs
by denormalfloat 2432 days ago
It's poignant to watch Go slowly realize the why other languages have more powerful, more useful error types. The `error` interface is anemic to the point of uselessness. Making it unwrappable is step forward, but all that does is progress it the level of 1995 Java with their "cause" field. It still can't express suppressed errors (like those from `defer f.Close()`). There is no standard mechanism to include an optional stack trace. Worst of all, rolling your own error type and trying to use it everywhere is near impossible. Every method signature has to use `error` due to lack of covariant return types, and heaven help you if you return a nil struct pointer, which will be auto promoted to a non-nil interface. Perhaps in Go 3 they'll get it right.
3 comments

I honestly don't understand where you're coming from.

Rolling your own error library is trivial. We have a great one at work that works great, prints stack traces, interops with normal error handlers, and does a lot of custom work for translating errors into external customer-visible messages and internal developer messages. We use it at every layer of a 50-60 grpc microservice ecosystem without much headache.

Catching deferred errors is trivial, not sure what you mean there:

defer func () {

  err := f.Close()

  ...
}()

Printing stack traces is like 4 lines, I was able to implement ours in 60 minutes of doc skimming. Now it'd be 5 minutes.

I don't understand your last two sentences. It doesn't reflect anything I've run into in the real world, I don't think.

I have a few gripes about golang, but the minimalist error interface is not one of them.

>Rolling your own error library is trivial.

And right there is where you’re going to lose most people.

If it’s is trivial, why isn’t in the standard library? If everyone needs to do it, why not standardize? I love Go, but i have to agree with grandparent poster.

> If it’s is trivial, why isn’t in the standard library

Getting a stack trace is available in the runtime/debug library.

Perhaps stack traces aren't in normal errors since a program can throw 1,000 errors per second in a perfectly functioning application and that could get expensive.

I have heard that performance is the reason but I'm not able to confirm that.

github.com/pkg/errors is fairly ubiquitous and includes stack traces.

> If everyone needs to do it, why not standardize

The only time I've needed to read the stack trace in go, is when there has been an unexpected panic. Otherwise my error messages are more than sufficient to find the root cause of the error. I only have to run my own code though, I imagine if I was debugging someone elses code the stacktrace would be invaluable.

Go has purposely tried to do a few things differently. Date format comes to mind, and lack of exceptions which I personally like.

Because everyone needs something different and failure is your domain: https://middlemost.com/failure-is-your-domain/
> If it’s is trivial, why isn’t in the standard library?

Trivial does not mean generic. Just because error handling is easy doesn't mean I want the same handling as you do.

Somehow I don't buy it. If you don't want rich standard for error handling, nothing prevents you from returning just a string as an error. It's just a value, isn't it?

Error handling is generic by itself. This is at the heart of any existing application. It is fundamental part of the design process and later on contributes greatly to troubleshooting. Making this solid should be, IMHO, one of the most important and thought through part of any language. In Go it seems to be left at the developer's convenience. And even though you may say there is a huge debate on the subject, it always leads to nothing. Or almost nothing, like in this case. It's just disappointing.

You can't standardize until people agree. The trivial part isn't the code it's what is acceptable to everyone.

If people don't agree then it by definition does not belong in the stdlib.

*the hard part isn't the code

I guess I finished that sentence in a different way than I started it.

People disagreeing is even more the reason to standardize for things that matter to the broad ecosystem.

For example, no one likes gofmt (tabs, eww), but everyone likes code across packages looking the same. And so we use it.

Speak for yourself, Go is a bastion of formatting sanity for me cause they chose (correctly) to use tabs. Tabs for indentation are the obvious choice to improve code readability and accessibility for people that want different indent sizes. I don't need it (usually), but I've met people that prefer everything from 2, 4 or 8 spaces, and have heard of people wanting everything from 1 to 8. It also handles far more sanely then spaces for people using proportional fonts instead of monospace, another common readability/accessibility tweak.
and gofmt existed before go really had a large community. It's also a very different problem because you can change formatting decisions from one version to another (and they do!), but API's are difficult to change.
Regarding "Auto promoted to not-nil interfaces", here's what it looks like:

https://play.golang.org/p/7Gs76Nt-h4b

That issue is one of the reasons everything has to return 'error', not your own struct that might be more convenient to work with. I've run into it in the wild in dozens of codebases, so it's definitely a real issue.

Without generics and covariance, it's an uphill battle to create usable monadic error handling in Go. If all you've used before is C which has int returns and globals for error handling, I can see how go's error handling looks nice, but compared to most languages invented in the last 20 years, it feels far worse.

One way to work around that is to use interfaces (https://play.golang.org/p/aAEAi8GvDkv), but either way, you are "shadowing" the type, and can no longer use the .TraceID in the second function cause it's just a plain error at that point.
> We have a great one at work that works great, prints stack traces

Sounds like the C and C++ situation where each company and large project rolls out their own string implementation.

That was their itch to scratch. Please tell me about your language which has every single feature you need built into the standard library?
Python, Java and .NET, pretty much.

There are still stuff missing, but not basic low level stuff that everyone uses daily.

It can be in the std, or it can be available as a package in your language package manager. But Go doesn't shine in dependency management either.

Minimalist standard library doesn't go well with their philosophy around dependency (“a little copying is better than a little dependency”). That's why the made so much stuff in std.

Pretty sure modules addresses the dependency management concerns, no?
How do you convince every other library that you might sandwich on your call stack to use your error library so your error data gets passed through correctly?
Well, at this point, don't you just wrap the error? Isn't that the point of this post?
And stack trace in the middle?
The other library doesn't have to use your error library, it just needs to preserve your error when passing through. To make this a common practise, this proposal was made.
Everyone should not have to have their own error library for basic functionality or custom foo to print stacktraces.
The idea of carrying around bloated errors everywhere is absurd to me, especially in a microservice ecosystem where that all has to cross the wire, potentially multiple times.
You just return codes between IPC or inter-service communication. No one is asking you to serialize error objects and send them.
While Go has issues with lack of functionality in error handling, I think the biggest win has been treating error as value and returning error via multiple return values paradigm. It forces the programmer to be aware of the possibilities of errors when using functions and explicitly handle them.

When working in other programming languages, this is something I sorely miss and have to resort to ugly and non intuitive try catch constructs which feel like "bolt-on magic" rather than a natural part if the program.

IMO this feature makes up for most of the stuff that's lacking.

I'm not sure where you're going with the stack traces complainr. It is pretty trivial to get it in Go.

When you start thinking about every function having multiple return values, one of which is an error optional, treat every one of those function as failable, and then build that functionality into the compiler - then you soon arrive at proper exceptions. Granted, forcing the error machinery syntax upon the programmer makes it more explicit and requires less discipline, but it also gets old pretty fast.
Yes. Exception is generally a decent solution to error handling unless you're using RAII languages like C++/Rust where unwinding the stack doesn't work well with destructors.
Except it doesn't force you to be aware of them or explicitly handle them. It relies on a programmer having the discipline to handle them, or even just the inclination to do so. Fine in a perfect world but most of us work in an environment with unreasonable deadlines, we are tired and eager to get home on Friday afternoon, mistakes happen.
> I think the biggest win has been treating error as value and returning error via multiple return values paradigm.

Treating errors as value is cool (no exception!) but using multiple values not so much, the proper way to do so is with product types, but Go's type system don't have them unfortunately…

Do you mean sum types? Like Haskell's Maybe and Rust's Result?
Sum types indeed, thanks. That's why I shouldn't post before breakfast.
It would be great if Go had sum types for error handling, but errors-as-values is great. And the conventions around error handling are pretty easy to work with—the biggest issue is still the anemic error interface (no standard for stack traces, just a string error message, etc).
I agree that error handling has been one of very few rough points for Go. I think the scorn people pour out onto Go is undeserved. It’s easy to say that they didn’t learn from other languages’ experience, but much of that “failing to learn” is a feature. To its great success, Go failed to learn inheritance, “objects” (as in everything is a fat pointer with data, locks, and vtables), exceptions (as control flow), convoluted build systems, gratuitous design patterns (abstract factory bean), etc. Yeah, Go isn’t perfect, but it’s one of the best languages on the market at the moment.
The hate for Go here is so amusing. It's a simple, stable, performant language that keeps getting better over time.