This (it looks like to me) is an attempt to pull in the best bits of Dave Cheney's errors package (which I love) into the standard library: https://github.com/pkg/errors
Standardising error unwrapping is a great idea IMHO and I think that this has a lot of merit.
I don't like the `fmt.Errorf("more description: %w", err)` though for several reasons.
Firstly it is a lot more opaque than Dave Cheney's original mechanism errors.Wrap(err, "more description"). You've got to check the format string for a `%w` to see if it is wrapping an error or not.
Secondly why is this really important functionality in `fmt` and not in `error`?
And finally we've been encouraged to write `fmt.Errorf("more description: %v", err)` (note `%v` not `%w`), so I think there will be a lot of unwrapped errors.
...
I'm not sure enough has been thought about the backwards incompatibility. With rclone I try to maintain compatibility with the current go release and a few previous ones so that rclone can remain running with distro go versions and gccgo both of which are a bit behind. For something as common as error handling this will cause lots of libraries to suddenly be no longer usable with anything less than go1.13.
IMHO I think this would be better staying as a non standard library package for the time being while more kinks are worked out.
Also note that the library checks for `: %w` at the end of the format string to enable this wrapping feature. If you use "%w" anywhere else in the format string it won't work.
The %w (or %v) identifier must be at the end for the magic to work, but you can still meaningfully format things in front of the wrapped error, e.g. fmt.Errorf("Write(%s): %w", filename, err)
+1 on all of this. I really prefer that https://github.com/pkg/errors is outside of the standard library so I don't need to use the fmt.Errorf abomination.
IMHO they should either just adopt Dave Cheney's errors package (https://github.com/pkg/errors) into the standard library or leave it as it is becoming the de-facto standard.
A bit offtopic how does pkg/errors compare to https://github.com/juju/errors/? I see the former is much more popular but the later seem to be nicer, I especially like the deferred annotations, since it's easy to use them to include function arguments in the stack trace.
It seems like golang designers are totally isolated from what's been happening in the last 30 years in languages design. They still insist on their weird way of error handling just like they were stubborn for years and years on the lack of package management and eventually a very weird and rudimentary way of it. It's sad because I use this language extensively but its weirdly mediocre design is totally unfathomable. It's like they are very stubborn to do anything but the right thing.
I've been using Go heavily over the last five years, starting with 1.2. Whatever warts the language has in its design, the error handling being one, it's still an excellent language for getting work done quickly, and correctly. I'm far more productive in it than Java, C++, or Python, and I'm old enough to have done lots of Pascal, Lisp, and more scripting dialects than I can count.
Having been writing software over the 30 years that you mention, it's clear to me that Go has not been isolated from language design, it's just made a choice to stay simple. Java, C++ and Python have exceptions to keep you from littering your code with error handling, and code with exceptions is hard to read. The line of code you are looking at any point in time may instantly jump to another place, effectively, having "goto"'s evil cousin, the "comefrom". Somewhere, in your code, is a "comefrom" statement which runs immediately after the statement you are looking at, you're not aware of its existence. My Java/Python for production code ends up looking a lot like Go in terms of error handling, because you have the most context about an error at the point where it happens, so that is the best place to deal with it. I believe that Go 1.x will have the staying power of C.
> Java, C++ and Python have exceptions to keep you from littering your code with error handling, and code with exceptions is hard to read. The line of code you are looking at any point in time may instantly jump to another place, effectively,
Rust and Swift both manage to make error handling easy while keeping this nice "errors are just values" property. Go could easily have added Rust-like or Swift-like error handling backwards compatibly with their existing idioms without falling back on weird special cases of format strings, but have chosen not too.
This does not make the language simple (even though in other ways it is).
Firstly, rust has sum types, so it's possible to have exhaustive matches and know you've handled every case. This isn't possible in go. For comparison:
// go
val, err := doSomething()
switch err.(type) {
case *SomeErrorType:
// handle
default:
panic("inexhaustive error check (at runtime, only if that error type is hit)")
}
// rust
let (val, err) = do_something()
match err {
Err::SomeErrType(inner) => { /* handle */ },
}
// won't compile if the match isn't exhaustive
Note as well that you have to return the error interface, not some more specific type, in go because of the "nil struct is not a nil interface" gotcha. Juggling around structs that are returned as errors is basically impossible to do safely, so everyone returns the error interface. This is another way the language causes error handling to be worse.
Next, generics in rust allow for nicer chaining of computation in the presence of errors. Let me show you two examples. Again, the go and rust code is as identical as I can make it:
// go
val1, err := computation1()
if err != nil {
return nil, err
}
val2, err := computation2(val1)
if err != nil {
return nil, err
}
return computation3(val2)
// equivalent rust
computation1().and_then(computation2).and_then(computation3)
// also equivalent rust
let val1 = computation1()?;
let val2 = computation2(val1)?;
computation3(val2)
The ability to have a generic 'Result<T, E>' type to chain computation allows for code to be more readable, while still having all the benefits of errors being values.
The ability to have a generic 'Option<T>' instead of 'nil' also is very helpful, but enough has already been written about null pointers that I don't wish to rehash it here.
Finally, in practice, rust's higher level features (namely macros) allows libraries to create very powerful developer abstractions around errors, like those offered in the 'failures' crate, all without having any runtime overhead.
In practice, all of these things also combine to result in libraries offering better error types and allowing callers to handle errors well.
Aren't you supposed to check all the possible errors in golang anyway? How's that any different than java code being littered with error handling code?
Except it's possible to miss checking errors in golang (accidentally either by not assigning the return value, or by overwriting a previously assigned error). Whereas in a language with exceptions, this is not possible unless by explicitly adding code to ignore exceptions.
Do you ever see golang code that handles errors returned from fmt.Println?
I think I agree with you - I'm just trying to understand the other perspective from someone that seems to think not having exceptions is an improvement. I've mostly worked using java and go and to be honest I think I prefer exceptions because you should (almost) always check error codes in go anyway, and if you forget, you just made debugging much more difficult.
I keep checking in on Go, trying to decide if I want to add it to my toolkit, but this argument usually bugs me.
Lisp is simple. Go is half-simple, but in its take on simplicity, it also takes away some constructs that make useful, high-leverage expressiveness impossible.
Some of its other benefits are appealing to me, but that's not one of them. It seems simple on its face only.
My takeaway from the last 30 years in computer science is that errors are not exceptional. They occur often and should be accounted for near the code that generates the errors.
Exceptions and return values are both sub-optimal. Exceptions encourage drastic actions for non-drastic events (exit the program if an HTTP server is transitently slow). Return values encourage ignoring the error value, and then wondering why your program broke. Special types that wrap error values or exceptions cause the same problem; when you want to defer something, your code becomes contaminated with the error type (f(x) returns an error, g(x) calls f(x) but doesn't feel like dealing with the erro, so now g(x) returns an error... and all the way it goes up to the top level.)
Overall, I don't see a grand unified solution to this problem. We should make it possible for functions to declare everything that goes wrong so that recovery can be more easily tested. No language does this; they often merge vastly different errors into the same type, so the programmer is powerless to understand the possibilities. Consider two database errors; "syntax error" and "transaction aborted, retry it". Typed error systems typically condense that to a "database error", but how your program should handle the two cases are vastly different.
Anyway, I'm happy with the way go works. If I don't explicitly know how to handle an error, I wrap it with a tag and return it. When I look at the alert / error logs, I know which codepath caused the problem and can investigate. For cases where I know how to handle an error, I can explicitly deal with it (yes, often with strings.HasSuffix to find the one I know how to handle). That is all I really need. If it were an exception, the code would be basically the same. So I think it's a red herring to complain about values vs. exceptions. Neither system prevents you or encourages you to write correct, robust code. If we want to do that, we need completely new tools.
Rust's type system + the failure crate (https://crates.io/crates/failure) is the nicest I've seen. It's similar to Haskell in that errors are part of the return value of a function, and the type system enforces handling of this.
_But_ Rust also includes some really nice syntax for passing errors through so I can write this:
The `failure::Error` type automagically wraps any error. That means I can go through many levels of my stack returning `failure::Error` and at any point in the call chain I can decide to examine the error type instead of writing `some_func()?;`, which would pass the error back.
The only part I don't like is that I need to add a `?` when I return my own errors. I don't totally understand this but from what I sort of understand the reason is that `?` invokes `.into()` on the returned object, which is what lets me return a (wrapping) `failure::Error` instead of a `MyPackageError`.
The upshot of all this is that "ignoring" errors is quite easy, as long as _somewhere_ in my call chain I actually do handle the error. The type system will enforce this for me, as attempting to treat a `Result<String, Error>` as just a String will cause a compile time error. I have to unwrap it and either ignore the error (which would lead to a runtime panic if there _was_ an error) or do the right thing, which is to explicitly handle both the ok and error cases.
I'm working on a CLI program using this system, and I can bubble all the errors up to the main entry point of the CLI. At that point I can turn errors into a print to stderr and an appropriate exit code.
The two are closely related concepts. You can model checked exceptions as a secondary return type, except without the same first-order representation (e.g. inability to specify checked exceptions in generic terms).
> My takeaway from the last 30 years in computer science is that errors are not exceptional. They occur often and should be accounted for near the code that generates the errors.
This is exactly opposite my experience. In 90% of cases there is no way to recover from an error locally and I should just fail at the highest level, possibly retrying a few times.
If that's correct, then our systems are built on false assumptions. SSDs should not see an ECC error and write the data to a new block; that error should kill your program. TCP should not detect packet loss and retransmit, it should kill your program?
My point is, a lot of good error handling is built at the lowest level, close to the point of failure. That code knows what the problems are and how to fix them. Errors are not the exception, they are the rule. If you handle it well, people won't even know how unreliable the underlying system is. (NAND flash? The first time I used raw NAND flash I was blown away at how unreliable it was. How do computers even work at all like this, I thought. Then I realized... that's why so much money is poured into things like SSD controllers. To hide that fact from the user and make them happy, even if the raw technology it's built from doesn't offer perfect reliability.)
>My point is, a lot of good error handling is built at the lowest level, close to the point of failure.
This is very true for some types of errors and for some types of programs but not true at all for others, which is why this debate has been going on and on for decades.
An extremely common example for the latter is programs that touch a DBMS or file system on every other line. You don't want to see error handling code for "database is gone" or "local disk is gone" events all over the place.
The information required to handle these errors just doesn't exist in the local context. So the only reasonable question is how to let those errors bubble up to some central location where you can handle them or decide to abort.
In my opinion there are good arguments for and against both exceptions and error returns. But if error returns are used, then there must be some reasonably ergonomic way to do it, i.e not what Go does right now.
I just don't agree with adding more syntax to the language for what basically amounts to an if statement. If it's a conditional, which it is, just use an if statement.
Look at all the monad tutorials for Haskell. Nobody knows how to use the special syntax for Maybe and Either. They read hundreds of articles and still don't get it. Meanwhile, everyone understands how "if err != nil" works, perhaps too well, which is why they complain about it.
> My takeaway from the last 30 years in computer science is that errors are not exceptional. They occur often and should be accounted for near the code that generates the errors... We should make it possible for functions to declare everything that goes wrong so that recovery can be more easily tested.
> Accidents happen. What he meant is that they must happen. Worse, according to Perrow, a humbling cautionary tale lurks in complicated systems: Our very attempts to stave off disaster by introducing safety systems ultimately increase the overall complexity of the systems, ensuring that some unpredictable outcome will rear its ugly head no matter what.
If you don't like it, don't present pale echoes of criticisms we've already seen (not listening to 30 years of language design...), tell us what you would do instead, because the answer is non-obvious and there is no one 'right thing'.
What a ridiculous way to think about language design. The users of a language are best suited to define what parts of the language do not feel good to them - why are they expected to come up with solutions for the language designers? In your mind, are you only allowed to present criticism if you're also able to define solutions? Doesn't that limit the people that are allowed to criticize to the language designers themselves? That seems like an excellent way to get tools nobody wanted, no?
Language design has nothing to do with discussions on HN (at least I hope not!).
It’s very easy and not very useful to come up with vague criticisms based on generalities, it’s much harder, but infinitely more fertile and interesting, to come up with a coherent thesis as to what should be done. The OP posited a right way, I’m curious as to which of the right ways they mean.
There are different kinds of discussion, I don't think a discussion site is the right forum for design, do you?
Also discussion requires some content, not empty complaints without any backup. For example propose a different mechanism for reporting errors, like optional types or exceptions (both have potential problems). The OP would be more interesting if it actually tried to engage with the problem.
And I am so happy that they won't do anything but the right thing.
Over the decades as a programmer I have seen a lot of language come and go. A lot of languages suffer from "features". Where either a language is designed with advanced concepts getting either directly in the way of writing simple programs, or even worse, getting bolted on later on, creating something very different from the initial language.
I love the dependable simplicity of Go. It contains everything I need for a surprisingly large part of my work and not offering more than that is an asset. Few things in Go feel like they had been thrown in without proper consideration.
My statement was obviously inverting the statement the poster made I answered to and was meant abstractly. They did very hard try to only do the right thing and did not include a feature just for the sake of having that feature.
Of course, any humans are making mistakes in the process nevertheless, but overall I think they got it pretty much right.
Having said that, I would be curious what you have in mind when saying that they made plenty of mistakes?
No immutability. Nullability by default, and weird corner cases like nil slices and interfaces. Language semantics requiring weird stack discipline that is incompatible with normal ABIs, and causes overhead when you need to interop.
But frankly, just the design of append() alone should be a warning sign. If it were a low-level facility, fine, it's only needed to be understood by whoever's writing a library to wrap it for general consumption. But it's the idiomatic Go way of maintaining a sequential mutable collection! How is such a basic operation is so non-obvious that people still write articles to explain it, and so verbose that you must reference the collection twice to make it work right?
(Indeed, the language itself kinda acknowledges how clumsy it is, by making it an error to ignore the return value of append(). But, as usual with Go, it's hardcoded - i.e. you can't do the same for your own function. And IIRC, it wasn't there from the get go, but got added somewhere along the line.)
Besides never being an issue for me, GOPATH isn't strictly speaking a langauge issue, but an issue of the build environment, which will no longer be the default with Go 1.13.
What is your issue with GOPATH, that you call it a big one?
If you like Go, you'll love the simplicity of Brainf-ck, a clean design which is easy to learn, simple syntax, and supported in almost all computing platforms.
Sorry, I don't see how your comment contributes to the discussion. You have heard of C? A language which most of the Unix infrastructure is built upon?
It turns out, Go is a language, which keeps a lot of virtues of C, adding just enough to it to fix some of the shortcomings of C, adding more safety and convenience.
No, C doesn't have many virtues. It's major virtue was getting close to being a macro assembler, and Go removes that completely. Go tries to be a higher-level-than-C language with good execution speed, but that goal was already achieved perfectly by Object Pascal, D, Ada, and Common Lisp.
The brainfuck quip is a bit more potent when the Go advocate in question has made it clear that they think language simplicity and code simplicity are equivalent. That didn't happen here, but it's tempting to assume that's what the advocate is thinking when they praise language simplicity so much.
Go's error handling is essentially the same as Error/Either/StatusOr (returning errors as values).
Re: exceptions, there are a lot of people, myself included, who do not view exceptions as "advancements", but as setbacks. Magically and suddenly subverting the normal control flow and unwinding the stack in highly concurrent programs (as is expected in Go) is an awful way to do error handling, and you end up having to bypass that mechanism and pass the errors as values across call stacks anyway, so what are you really gaining for all the extra complexity, "implicitness", and cognitive load of doing that?
> Go's error handling is essentially the same as Error/Either/StatusOr (returning errors as values).
Except that you cannot easily chain calls that return errors, or it isn't really that hard to accidentally ignore errors because of shadowing or overwriting the variable you're storing your errors in. Or the fact that using a union type to represent errors is a strictly superior way, both in terms of usability, as well as correctness. People should just face the fact that returning errors as a product type is a mistake.
> Magically and suddenly subverting the normal control flow and unwinding the stack in highly concurrent programs (as is expected in Go)...
You may want to see what approaches Akka or Erlang takes here. golang authors just decided to ignore established practices and use clunky approaches to problems that have already been solved.
> Except that you cannot easily chain calls that return errors, or it isn't really that hard to accidentally ignore errors because of shadowing or overwriting the variable you're storing your errors in. Or the fact that using a union type to represent errors is a strictly superior way, both in terms of usability, as well as correctness. People should just face the fact that returning errors as a product type is a mistake.
These are still essentially the same. Errors are returned as values, with no major difference in runtime semantics. Whether the language supports union types is orthogonal to that.
> You may want to see what approaches Akka or Erlang takes here. golang authors just decided to ignore established practices and use clunky approaches to problems that have already been solved.
There's not one solution, there are multiple solutions with various tradeoffs. The tradeoffs Go made are congruent with its tenets as a language that values simplicity and low cognitive overhead.
> Go's error handling is essentially the same as Error/Either/StatusOr (returning errors as values).
Not really, it's very annoying to chain calls which can error. Also, as another commenter mentioned, functions which can potentially fail should return sum types and not product types.
Compare:
do first <- computeFirst
second <- computeSecond
return $ f first second
> Even without do-notation the Haskell is considerably shorter
This is a great example, not to your point, but to the methodology of Go. You give a verbose Go example, that I expect the vast majority of readers here could understand, even if they've never written a single line of Go, followed by a terse but syntax-heavy Haskell example that I expect relatively few could.
Apologies, I know very little Haskell - does that example mean that `computeSecond` will always be called, even if `computeFirst` failed? If it does, it's not the same as the Go code which will not call `computeSecond` in that case (which might be a requirement - who knows?)
I'm also assuming that `f first second` will return (an error monad?) if either of `first` or `second` are (an error monad?) - is that right?
That's a stawman fallacy. There is no perfect language. However, there are languages that almost are strictly superior to others. Java and C# in this case are almost strictly superior to golang in almost every front.
I mostly write Haskell professionally, but I still reach for Go depending on the project. Sometimes it just has the libraries I need and I can tell up front that I'm willing to deal with the tedium the language forces on me. Cost/benefit analysis.
I've heard great points and experienced myself how utterly legible go is. But nobody on earth has ever praised anything about the language design itself. I wonder if go came from somewhere not google- how would it fare.
It's kind of irrelevant since it's a different team with a different software niche and design aesthetic, but since you ask:
Dart struggled for years to find a killer app, though Flutter now seems to be a good bet.
Originally the idea was to replace JavaScript and everyone except TypeScript pretty much failed at that. (I mean failed from a popularity point of view, they often succeeded technically.)
I'll also point out that coming from Google is kind of a mixed blessing these days, from a popularity standpoint.
Dart was born "for the wrong reasons" AKA replacing Javascript, it failed at it. The language itself is quite good, better than Go IMHO, but Go has the advantage of not requiring a separate virtual machine. The problem with languages is whether they get enough momentum so that a community can be built around them. A language without an extensive ecosystem is nothing.
Also since Go is "hypocritically Object Oriented", people can claim it's not an object oriented language and kind of write Go like classic structured programming such as C. But Go is OO. You can't write go without using interfaces for I/O.
> You can't write go without using interfaces for I/O.
You can and people do that all the time. You don't have to use its standard library I/O APIs, even its syscall package.
I'm not disagreeing that Go is OO though. Its ecosystem is dominated by OO. But it's more like Perl in this regard, where you can spend years without writing a single line of OO code yourself even if you have to use other people's OO code.
Except that TypeScript's designer had strong and established experience in language design, and he made a lot of correct choices when implementing TypeScript. Can't say that about golang.
You could argue that there's a bit of a network effect involved. i.e. it doesn't get much love on HN, Reddit, etc. (just look at the comments on any Flutter thread).
> I wonder if go came from somewhere not google- how would it fare.
There was a precursor to golang that the some of the same authors worked on before they were at Google. It didn't go anywhere, precisely because it didn't have Google's name behind it.
Ever considered that in 1995 some aspects of Limbo weren't as appealing as they are today and that this played a great deal in its adoption?
For example its CSP concurrency model, similar to Go, was hardly pertinent when common processors were 150 MHz Pentiums with 1 core. Completely different scenarios.
Not to mention Bell Labs had enormous influence on Computer Science at the time so it's not like Limbo had no strong backing either.
> For example its CSP concurrency model, similar to Go, was hardly pertinent when common processors were 150 MHz Pentiums with 1 core. Completely different scenarios.
There was always demand for having servers that processed high numbers of requests (e.g. C10K). Just because single core processing was common does not mean that there wasn't need for high concurrency.
> Not to mention Bell Labs had enormous influence on Computer Science at the time so it's not like Limbo had no strong backing either.
In those days there was less fad driven development compared to what we see today. So the effect wasn't as pronounced.
> There was always demand for having servers that processed high numbers of requests (e.g. C10K)
Demand is not binary. In 1995 the demand for concurrency was a fraction what it is today. Not only multicore processing was an extremely rare sighting in comparison to what we have today but also:
1. Internet was accessed by 10% of the population vs today's 80%+
2. Capable smartphones? First iPhone came only 12 years later. There was no such thing as internet during commute.
3. C10K for example was coined only in 1999.
The 20 years between 1995 and 2015 did change the IT landscape wildly regardless of your beliefs.
From what I have seen, the Go 2 Error Values plan[1] has not received enough exposure to generate the level of feedback necessary to support a go/no-go decision re a major change in 1.13 -- one we cannot opt-out of, at that.
I suspect that the overwhelming majority of Go developers has no idea this is in the works. It was covered once on the blog last August in the Draft Design summary, when there wasn't any code behind it. It was not mentioned in Go 2 Here We Come[2], nor at the start of the below golang-dev thread. It was mentioned on golang-dev when I posted a link to the issue tracker in late January, but my posts would see a fraction of the attention vs those by Rob, Robert, Russ, Ian, et al.
There are outstanding issues with the current draft, specifically its performance[3] and API[4].
If it lands in 1.13, please give it Experimental status, with a build or env flag to disable changes to existing APIs, and perhaps a way to re-enable them on a per-function or per-package basis.
As others have stated, this seems incredibly odd to me:
> If the last argument is an error and the format string ends with ": %w", ...
This seems like a magic-string kinda hack to me. I like the idea of wrapping errors so that you keep the stack and full context, especially since you may need additional structured data from all errors (e.g. DB error, access error, ...) to produce user facing messages, so IMHO the wrapping should be more explicit than just %w.
Edit: Instead of making another post, I'll add this here. It also feels odd that concerns about xerrors.As possibly panicking at runtime are considered addressed by the addition of a go vet check: https://github.com/golang/go/issues/29934#issuecomment-46252...
I am not opposed to go vet, to be clear, but one of the aspects of the go compiler is that it does not warn, only errors out if compilation can't proceed. But the design of xerrors.As is fine because go vet acts like a compiler warning might? This is an aesthetic complaint more than anything else, but it still doesn't sit right with me.
My apologies, I seem to have confused the two packages. I don't see it in the godoc either. In which case I echo your desire to have an explicit method of formatting and wrapping.
I suppose it won't take long for a component of one of the gometalinter or golang-ci or whatever to develop a "Errorf used without %w in the final position" warning, which will be good enough for me, but it would be better to have something like Wrap officially, IMHO.
Yes, it's a magic string hack. On the other hand, it's concise and easily learned. This is kind of the opposite of the "if err!=nil" dance that many people complain about.
One of the things I picked up from Dave Winer around 20 years ago is that errors messages should be of the form "Can't X because Y", and while I don't quite always use that exact grammar form, my errors pretty much all take that form when there isn't some other overriding local standard for errors. It's not a bad template. Most error messages will give you some hint about the Y, but the difference between "Error: file system full" and "Error: couldn't open log file '/var/log/myapp.log' because: file system full" is pretty substantial, especially if system state has mutated in the meantime and by the time you get to the system, none of the filesystems are full anymore.
Descriptive errors that match the business logic are ideal in many cases. Clearly, one shouldn't don't divulge "secret" validators.
More on the topic, I'm really happy to see this coming in so far ahead of 2.0. I want to get ready for 2.0 because of the gains I hope to see in idiomatic go in 2.0.
This is the general strategy when developing "2.0": do as much as possible in a backwards compatible way, and if we never need to make a breaking change, never call a release 2.0. The new features process that was introduced recently may result in a 2.0 release, or it may result in 1.999, so you will continue to see new features in each release for a while regardless of what the release is called.
The biggest issue I have with most error messages is that they say things like you have here but don't reflect back the data that caused the error, i.e. show me the phone number you think I sent you.
I think this context is really hard to carry around, and if you aren’t extra super careful you can end up leaking passwords and other secret bits of memory here to logs. Better to just give me a place to put a debugger breakpoint IMO.
I agree with the feeling, but I also see this being a debate between an opt-in approach (choose to use an error value with gradually more information enclosed) vs opt-out (throw an Exception, a la C#/Java, that by default stores everything you may or may not need, then maybe figure out a way to stop storing what you don't need). The use cases are not going to magically go away though, which explains why we need the solution to be somewhere in the middle.
> then maybe figure out a way to stop storing what you don't need
I'm not sure I understand why this is something you need to do. "Figure out a way to stop storing what you don't need." Who cares if you don't need it, or at least don't need it now, storage is cheap.
Exactly. Plus, code that uses exceptions but does not encounter any throws should be faster than code that uses error checks. In the latter, there's always a cost even if there aren't any errors returned. However, the former can optimize for the (hopefully more common path) of no exceptions being thrown and avoid checks altogether.
Source: I worked for Microsoft on C# projects where GC was constantly the bottleneck. Reducing number of exceptions (especially those used for control flow) helped.
Weirdly enough this perspective might suggest that the problem is not actually with Go, but with object oriented programming. Which wasn't considered thoroughly by original authors, because they were not OO programmers.
No other paradigm appears to be able to handle the complexity required to build the systems of today. Objects are very natural way to organize complexity. It's what let UNIX succeed where Multics failed.
Not sure why people are bashing Go because of it's choices.
Looking forward to shit storm once they introduce generics in near future. I am sure people will still complain that this is not how parametric polymorphism should be implemented.
I think this is a positive development and should be cheered by Go programmers. Slowly but surely they progress on the road to implementing Exceptions. (ducks and runs like a scaredy cat to avoid the wrath of Go Fans)
What I'm really looking for is being able to debug any production Go binary by inspecting the stack trace behind an error. Could be gated by an environment variable.
Standardising error unwrapping is a great idea IMHO and I think that this has a lot of merit.
I don't like the `fmt.Errorf("more description: %w", err)` though for several reasons.
Firstly it is a lot more opaque than Dave Cheney's original mechanism errors.Wrap(err, "more description"). You've got to check the format string for a `%w` to see if it is wrapping an error or not.
Secondly why is this really important functionality in `fmt` and not in `error`?
And finally we've been encouraged to write `fmt.Errorf("more description: %v", err)` (note `%v` not `%w`), so I think there will be a lot of unwrapped errors.
...
I'm not sure enough has been thought about the backwards incompatibility. With rclone I try to maintain compatibility with the current go release and a few previous ones so that rclone can remain running with distro go versions and gccgo both of which are a bit behind. For something as common as error handling this will cause lots of libraries to suddenly be no longer usable with anything less than go1.13.
IMHO I think this would be better staying as a non standard library package for the time being while more kinks are worked out.