118 comments

[ 3.0 ms ] story [ 177 ms ] thread
Not to be facetious but water isn't wet.
I love Go, but error handling feels like hand-carried, checked exceptions. And now they figured adding a “cause” field is practical. I’m getting deja-vu with Java exceptions 15-20 years ago.
I consider checked exceptions in Java quite a nightmare. You have to specify the classes of exception you might throw as part of your function signature and in all methods which might propagate. This is quite a buerocratic monster, which requires quite a bit of refactoring if your exception signature changes. Alternatively, all methods in the chain tend to catch all and then rethrow others.

The described changes could have been added by anyone in earlier Go versions. These are not things which can be only implemented by the language or compiler implementor, these are just APIs, which are standardized.

You could just use throws Exception instead and check the error like Go does. Its a code smell in Java but so is a million exceptions in your method signature. Most libraries and apis will use a base exception type so keep this to a minimum.
Yeah, there are plenty of ways to abuse and misuse those as well. On the positive side, it's possible to make one piece of error handling for e.g. opening a file and writing to it. Just handle IOException. In Go, I have to do manual error handling for each step that may go wrong, ie. the file does not exist, program doesn't have permission to write to etc. In many cases I just want to handle the simple case of "unable to write to file for some reason".

My thoughts on Go error handling has made me conclude that there is probably no really good way to do it. Every approach will come with a load of downsides.

Go’s treatment of errors as values has served us well over the last decade.

Has it? As a Java developer who needed to write something in Go, this has been quite a frustration for me, personally. In Java, when I get a 100-line stacktrace error, my IDE analyzes it for me and I can click through the code and follow what's happening. In Go, I get a string from the developer. Hmm...

I agree, a simple string from the error doesn't help debugging at all. This happens a lot in production environment, in which developers are too lazy to write any useful debugging information, all we got left is a simple "get_foo() returns error" in the log, it doesn't help with the debugging at all !!!
Sound familiar. I had a boss that wanted to clean up our error messages and stack traces from our library/api and make them cleaner and more useful.

To make it cleaner, he wanted to absorb the whole stack for any error raised in our library and only print the error text. To make them more useful and helpful, he wanted us also include some guides on how to debug and solve the problem, based on the error, in the error text.

Fortunately I was able to convince him this would make things difficult for everyone, in a calm collective way.

How do people come up with ideas like this?!
Step 1) hire a manager for people management skills and no technical nouse

Step 2) Hire a lot of yes men

At least in OPs response, their manager defers to those who know better.

That's bad Go design. An API where the particular nature of the error matters, should declare error constants and use the "Is" mechanism. You shouldn't be unpicking the internals with an IDE, you should be getting something you can work with, right there.
>That's bad Go design.

I think that's the parent's point.

Though, not that that is bad Go API design on behalf of the library authors (as you imply), but that that is a sign of Go's own bad design -- the fact that it leaves it to programmers own devices to get their API design right with respect to errors...

> That's bad Go design. An API where the particular nature of the error matters, should declare error constants and use the "Is" mechanism. You shouldn't be unpicking the internals with an IDE, you should be getting something you can work with, right there.

AKA roll your own stack trace library. That's what "errors" package and co are. That's not "bad God design". It's just not Go's problem, as deemed by Go designers.

Thus criticizing that trade-off is fair, like any trade-offs.

So we're back to C where each project rolls out its own string implementation. This really shows the mentality of the golang authors.
I mean at that point if you are still using Go, then you agree with whatever Golang authors tell you.

Most people who were sceptical about Go or were vocal about trying to improve the language moved on to something else, because they faced a wall of contempt. The whole Go 2 stunt is more an "idea parking lot" than anything else.

First of all, there are plenty of stack trace libraries to use. Then, the Go creators gave you the chance to roll out whatever mechanism you want, because they made "error" a minimal interface. This is actually quite a feature. You are not forced into a complex mechanism which might be badly fit to your requirements. And finally, rolling out a "stack trace library" should be like writing 2-3 functions which you can use all around in your project.

The interesting thing about the new error APIs is, they are just auxiliary APIs. Every user could have written them, they are not mandatory to be in the core language or even in the compiler. They are just added as a standardized guideline how common tasks are handled and especially are standardized across packages from different maintainers, if they decide to follow that convention.

That isn't a stack trace, it's wrapping a specific complaint around a category of complaint.

The Java equivalent would be a specific FileNotFoundException with a text mentioning a file name, versus the general class hierarchy of FileNotFoundException -> IOException -> Exception which is routinely used for "Is" tests in Java.

Java is the worst language to do error handling. Let's just throw an exception and hope that somethings catches it!
Go is much better! Let's just return an error, and hope someone looks at it.

Oh, and if that hope was wrong in Java? Thread does, nice call stack in logs. In Go? Well, the program will go on with some default - value struct, most likely.

> Let's just return an error, and hope someone looks at it

Some people would argue that this is what linters do: warn you when you don't check errors as return values. I'm not sure what's the best solution here, and I don't think any language handles this out of the box (without linters)

> and if that hope was wrong in Java?

Denial of service

But I get your point though, errors are hard and I think the only language that I've seen doing something good with them is Erlang. You just expect errors to crash your actors, and you design to recover quickly from any type of crash.

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.
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.

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.
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.
>I honestly don't understand where you're coming from.

Perhaps they're coming from experience with better languages?

>Rolling your own error library is trivial.

And having to do so is a huge smell, if I ever smelt one...

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.
> 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?
>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.

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.

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.

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.
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.
*the hard part isn't the code

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

> 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.

> 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.

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.
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.

> 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.
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.
(comment deleted)
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).
Can someone help me with what seems to be a basic problem? How are you supposed to get stack traces for errors in Go? You can wrap an error every time you return one, but this omits useful information like the line numbers. Not to mention, a significant amount of the code you are writing is going only to providing helpful error messages.

Alternatively, it looks like there are libraries out there[0] that will include stack traces for you. It seems weird to me that it's necessary to use an external library to get what seems like it should be built into the language.

Can anyone enlighten me?

Edit: Forgot to include the reference:

[0] https://github.com/pkg/errors

github.com/pkg/errors is a popular solution to this problem
Indeed! In my opinion, Dave Cheney's error package is a better design than the Go 1.13 built-in mechanism, because it's more explicit. It's easy to forget a %w after being so used to %v, but errors.Wrap(err, ...) is pretty darn clear, as is errors.Unwrap.
As far as I'm aware, there are only two native ways to make an error unique enough to trace: Wrapping it with a unique string, or throwing a panic. You might be able to do something clever with panics by throwing them and then catching them to resume the thread, but I've never seen this done outside a few places in the Go standard library.
IIRC this is the suggested way to get a stacktrace. Panic and either recover or fail.
What? You're gonna need to link a source for that, because everything I've ever read is that panics are for catastrophic failure and should not be used for routine error handling.

Rob Pike has this to say:

Our proposal instead ties the handling to a function - a dying function - and thereby, deliberately, makes it harder to use. We want you think of panics as, well, panics! They are rare events that very few functions should ever need to think about. If you want to protect your code, one or two recover calls should do it for the whole program. If you're already worrying about discriminating different kinds of panics, you've lost sight of the ball.

Unless edited, I don't think that's what GP said. Panics are the way to get stacktraces, not the way to routinely handle errors.

Your top level comment about 403 errors made me assume you did not know about panic. Obviously your particular situation may have prevented it but I've always built Go programs from source (not linking binaries) which means I could always panic whenever I needed a stack trace.

Panics provide stacktraces, but are not the way to get stack traces. runtime/debug has the API to generate stacktraces.

github.com/pkg/errors is the ubiquitous library for seamlessly including stacktraces in errors

You can call runtime.debug.Stack() to create your error with a proper stack trace.
The way Go handled errors is why I made the decision to go with rust as my hobby/personal-open-source-projects language. It was just extremely tedious, despite Go being nice overall.

I love how error handling is done in Rust. Yes, implementing the From<T> trait can get verbose, but wrapper libraries like Failure or Snafu exist now as well.

Being able to propagate errors down the line seamlessly with the ? operator and Result<T, E> type is nothing short of phenomenal. It looks like the next version of Rust will seamlessly allow using ? on None types as well.

Look at the failure crate.

For lots of errors, just treating them as a generic failure makes code a lot better.

Which is supposed to be replaced by https://github.com/withoutboats/fehler
Fehler is highly opinionated and I'm not sure it's worth it. IIRC the author posted on the /r/rust subreddit and was unable to take criticism at all.

I don't like having to put decorators on functions and manually throw!()'ing. I would rather be explicit about returning Ok(T) or Err(E).

I did mention Failure/Snafu, but there are two problems with it:

1) The state of "how best to handle errors" keeps on changing. It was error_chain, then err-derive, then Failure, now Fehler or Snafu.. I would like to see this space calm down a bit.

2) Failure/Snafu require additional dependencies. For minimal libraries it's really not necessary to pull in so much when a a little boilerplate suffices. [0]

[0]: https://git.sr.ht/~andrewzah/hangeul-rs/tree/master/src/erro...

It already handles Option returns through ? shorthand.
I was on 1.36.0 which complained about the None type not implementing From<T> I believe. If 1.38.0 handles it already then that's fantastic.
Stack traces are desperately needed to give errors context, and I was very disappointed to see that proposal dropped from Go 1.13. At the same time, I'm hesitant to use xerrors in production code because it's likely to change rapidly in the future.

I had a situation recently where a fairly complicated API was returning an HTTP 403 in production. Once the error bubbled down to a level where it was actually logged, all that was left was "403 Forbidden". I spent days echo debugging and could not reproduce the problem. Finally, by chance, I happened on the problem while working on something completely unrelated. With a stack trace, 3 days of work would have taken 5 minutes. Wrapping would not help in this situation unless every single error was wrapped with a unique string, which frankly is ridiculous.

It's funny how people like to bash long Java stacktraces, while they help immensely in the situations like these. It's obvious from the start where the error is and all those stacks can help you to find out every little detail that lead to the problem.
I’m only familiar with people bashing long Java stack traces because (1) they are often hilariously long, with stateless web applications dumping tenfold the frames of a crashing webbrowser, (2) they contain some number of abstract factory beans (contributing to the length problem) and (3) they show up far more frequently in user-facing applications than just about any other language. Stack traces are great, gratuitous abstraction, buggy code, and exposing stack traces to users are not great.
The stack traces tend to be useful when there is a programming error in your program, and less useful when there is any other type of error. In Go, programming errors usually panic which will print a stack trace by default.

But I’ll see Java programs that give me stack traces just because I’ve misconfigured them, or the server is down, or I have a bad network connection, or a file is missing. This is awful UX. Worse, the stack trace might not tell me WHICH file is missing, or WHICH server is down.

You only get good UX for errors by manually annotating context in the places in your code where the context is known.

(My personal experience is that FAR too many actual Java programs in the wild will give me a stack trace when they should give a short error message.)

I feel like, in Go, the ideal is to handle the error in situ. If it has to be passed up, it goes the shortest possible distance before reaching code that can handle it. This is a difference from exception-throwing languages and so it changes the importance of a stack trace. You don't need to know where it broke because it broke here. The same approach as in C. You test the result where you made the call.
On the surface this sounds logical. But going back to the case I dealt with recently, all errors during authentication lead back to an HTTP 403 workflow. Errors themselves can occur a long distance from any sane central place to handle them. Unless you pass the http.ResponseWriter all the way up the stack, the only "in place" handling you could do is to log every error along with a unique string.

Is this really the best we can do?

    if err := frobulate(); err != nil {
        log.Printf("Error on line 123: %+v", err)
        return err
    }
You example is code smell.

When dealing with error handling in Go (and many other languages), you should do one _and only one_ of the following:

- if you have enough context to handle the error (eg. retry, or degrade gracefully, or perform whatever other application logic to recover, possibly returning a more friendly error to your caller), do that, possibly logging the source error and do not return it further up the stack directly - you have now healed the error condition, and the application code can continue.

- otherwise, return the error, decorating it with extra context if possible.

Doing both, ie. logging (which is a case of handling the error) and returning it up the stack leads to extremely chatty and difficult to debug code. Errors are a fact of life, and your path of handling them should be as well engineered as the happy life. That's one of the most important things in writing reliable software.

That's also one of the reasons there's no generic stack unwinding/exceptions on Go: it forces you to _think_ about error cases and at least reminds you to handle them gracefully. You might not like this approach of treating programmers this way, but that's one of the cornerstones of Go.

Well, sure, I agree with you. But then we're back to wrapping every single error in a unique string before returning it. Developers simply don't do that, so you end up with the un-debuggable mess that I had.
You don’t wrap every error in a unique string, only in places where there is additional context. Developers I work with do this.
And hundreds of others don't. "It works in my place" is never a good argument. Thing is that people are taking shortcuts. We have that in our DNA. If something isn't imposed on us, will be abused. I saw a code base of almost 2M lines, of which 1.5M is written in Go by seasoned developers. You'd be surprised how messy it gets over time and how many times people forget to handle errors or just decide not to, out of convenience. Not to mention other design issues, simply, because Go doesn't protect itself against almost anything. If Go authors counted on self-discipline of the devs then, sorry, not sure what planet are they living on.
> - if you have enough context to handle the error (eg. retry, or degrade gracefully, or perform whatever other application logic to recover, possibly returning a more friendly error to your caller), do that, possibly logging the source error and do not return it further up the stack directly - you have now healed the error condition, and the application code can continue.

> - otherwise, return the error, decorating it with extra context if possible.

This is precisely how checked exception handling works in Java, and yet there's no shortage of people complaining about it and praising how Go "makes you deal with it the way you're supposed to."

> it forces you to _think_ about error cases and at least reminds you to handle them gracefully.

So do checked exceptions. And 99% of the time, the solution to an error popping up in Java is to blindly re-throw it because hey, I'm just a component buried a couple levels deep in a REST call, what could I possibly do to fix the problem?

Interestingly you bring checked exceptions, i used to dislike them at the past but in recent years i came to appreciate having a visible flow of execution (and as such i prefer the C approach of explicit returns - note that i haven't used Go at all, i'm just curious about it) which made me think that Java forcing you to acknowledge any exception that may pass through and actively make a decision about it (handle it or explicitly pass it through) is a good thing. Though it also made me dislike its decision for runtime exceptions since they essentially bypass the checking (on the other hand they'd somehow had to handle things like NullPointerException, otherwise everything would need to declare that it would throw it - though perhaps that is a side effect of Java having nullable pointers and a different language without nullable pointers wouldn't need that... but that'd only fix the NullPointerException case).
Error handling is hard. Checked exceptions and Go’s error returns are both good attempts to solve it. They both have plenty of drawbacks but they’re really different approaches here.

> This is precisely how checked exception handling works in Java, and yet there's no shortage of people complaining about it and praising how Go "makes you deal with it the way you're supposed to."

Just because people complain about Java’s checked exceptions does’t mean that their complaints are correct or that they somehow apply to Go’s errors.

The main complaints I hear about checked exceptions are that they force you to expose the types of all possible exceptions in the method signature. This creates unexpected API churn and design difficulties, because it’s difficult to predict which exception types you would want to expose from a particular interface.

You also have to choose which errors are programming errors and which are not when you declare the error type. This turned out to cause problems. NumberFormatException is an example.

> So do checked exceptions. And 99% of the time, the solution to an error popping up in Java is to blindly re-throw it because hey, I'm just a component buried a couple levels deep in a REST call, what could I possibly do to fix the problem?

In Go, blindly returning an error is done much less than 99% of the time, at least in the code bases I’ve worked on or read.

I really like how Rust authors did it. If you don't need to add additional context, you can use `?` after the expression that may return error and if it does, it automatically returns it. No need for endless `if err != nil` and effect is the same. It makes code base really clean and easy to work with.
What about the situation where you need to clean up a little but you can't "handle" the error yourself? A simple example being

    1. Open a file for reading
    2. Try to open a file for writing, but error
    3. Close the file that was opened for reading
    4. Propagate the error upwards (because the reason this function was called failed)
This is just returning the error upwards, as far as I can tell.

This is the pattern I usually see in actual Go codebases:

    infp, err := os.Open(infile)
    if err != nil {
        return err
    }
    defer infp.Close()
    outfp, err := os.Create(outfile)
    if err != nil {
        return err
    }
    defer outfp.Close()
    // ... do some stuff ...
    return outfp.Close()
Note that double-close is explicitly OK, this pattern just avoids losing the error.
Could you, please, expand on double closing? I can't find any good reason to do it.
On a file open for writing, close(2) may return EIO. If you ignore the error, you have silent data loss. So you must propagate the error.

The double-close pattern is just the easy way to accomplish this in go. You defer fp.Close(), and then return fp.Close(). The return fp.Close() closes the file and propagates the error. Afterwards, the deferred fp.Close() executes, but this simply returns an error (which is ignored) because the file is already closed.

The behavior of fp.Close(), the fact that it is safe to call multiple times, is documented in the Go documentation.

There are alternative ways to achieve the same effect but they require more code. So I use the double-close pattern everywhere, and then put a comment in every time I use it.

I don't find it unreasonable to log and return. You know what happened where exactly, but the returned error might be part of a larger process that needs cleanup of other kinds. I default to immediately logging and returning with all the code I write.
it is very noisy and hard to see what is happening if you log and return an error at every layer of the stack.

Like everything there are exceptions to the rule and at times it makes sense to do both, but it shouldn't be the norm.

I feel like the rule should be

1. If you can handle it, you probably should also log it since you know what it means and what's being done about it.

2. Otherwise, if you can add pertinent information, like for example the fact that the file you couldn't find was supposed to be the SQLite database, but can't handle it here, wrap and return. Do not log.

3. Otherwise, sticky fingers off, pass it up unaltered. Do not log.

Very little error logic in the context of a sessionless server requires handling close to the point of error - requests should either succeed or fail en mass for easier reasoning and retry from the client end, which means no recovery.

Which means you need to abort when you find an error condition, and roll back everything, all the way up the stack.

The more stateful your problem domain, the more likely you need recovery logic, and error codes start becoming more useful. Add in nondeterminism and error conditions are non-exceptional and exceptions are often not the best model for them.

So how you do it is kind of Erlang style: Signal a channel "I died", log it, and die.

You don't have to think in N-layers-deep call stacks, that's a Java way of thinking. Log the problem in sufficient detail where you found it, and stop.

This is probably a use case for Go's panic and recover mechanism, where you actually do want to blow away a whole piece of the running code.

The point has also been made that errors in Go are O(1) so they cannot have stack traces. If you use the new %w to build a context trace in O(n) only as you need to bubble up the error.
In my experience this just doesn't hold for errors originating from a library. Because a good library interface should mostly be a black box it makes it really hard to understand the underlying cause of an error from something like a networking library or a database library without logging everything. God help you if you have client code that must be able to return errors originating from a library or from the client code itself.

Stacktraces are probably the single most useful tool in debugging any issue and their absence in Go is one of my only enduring gripes with the language.

You're only saying that because of the lack of wrapped errors. Once they add exception wrapping, you'll be able to essentially rethrow errors in Go. You'll have the same issue as every other language modern language instead of the current situation (which is a lack of error wrapping).
> Stack traces are desperately needed to give errors context, and I was very disappointed to see that proposal dropped from Go 1.13. At the same time, I'm hesitant to use xerrors in production code because it's likely to change rapidly in the future.

By the way what's left of all the "Go 2" proposals? "generics" look like they are in limbo and so is the "try/catch" proposal. Has anything been implemented yet?

Pretty sure the try proposal was killed. Generics hasn’t seen much headway to my knowledge. Not sure what’s up with it.
> Generics hasn’t seen much headway to my knowledge. Not sure what’s up with it.

They don't want to do generics, they never have.

What is preventing you to attach runtime.debug.Stack() to your errors to get the stack traces?
Stack traces exist in Go today, though...
Great to see this getting standardized!

I maintain an error package that lets users use structured types for errors and error codes [1]. It is critical to be able to wrap errors without information loss. I used one standard, a Causer interface, but I will be switching to Unwrap.

I see complaints about stacktraces here in the comments. I recommend always using pkg/errors or some error package with stack traces.

[1] https://github.com/pingcap/errcode

Oh I wish Golang had something similar to Rust's Options and Results. This `nil` value is the source of many crashes in applications I've audited (due to applications forgetting to verify if the value is `nil` before acting on it).

Maybe something like this could be supported natively in Golang:

    func thing(arg bool) Error.Result(bool, error) {
        if arg {
            return Error.Ok(false)
        }
        return Error.Error(fmt.Errof("nope"))
    }

    func main() {

        if thing() == true {} // doesn't compile
        
        match thing(false) {
            Error.Ok(value) => {
                fmt.Println(value)
            },
            Error.Error(err) => {
                log.Panic(err)
            }
        }
    }
just for fun I wrote an ugly PoC for bool options: https://github.com/mimoo/Bool
Almost every language supporting inheritance suffers from lack of ability to check if an error is part of the derivation chain. I have implemented this using expensive dynamic cast in C++ and instanceof in Java.

Finally Go gets it ahead of the other languages!

`instanceof` in Java can be surprisingly fast. Plus, it seems that errors.Is and As rely on reflection, which is slow in golang.
After years of using Go, I came to realize that the ONLY people that complain about errors in Go are those who actually do not write any Go code and those who write Go code less than a year(ie. juniors). There is absolutely nothing wrong with errors in Go and the creators got it right form the start. Nobody is forcing you to use errors. You can use booleans if you want. You can panic(throw exception) if that is what you wish. To each its own. If you want more syntactic sugar, move to Java, it has plenty of it for you.
Do have have experience using a language with sum types and exhaustive case analysis?
IMO Rust really got errors right, a sum type is the way to go. Not exceptions, multiple return, callback arguments, or sentinels. Encoding the control flow into the type system makes your code much more robust.