Hacker News new | past | comments | ask | show | jobs | submit login
An Open Letter to the Go Team About Try (ardanlabs.com)
127 points by ingve on July 2, 2019 | hide | past | favorite | 111 comments



FWIW, I wrote a short response here: https://twitter.com/_rsc/status/1146128393542492160

I want to flag especially that there is not, nor has there ever been, a plan to "push this through".

People consistently say error handling is problematic for them in Go. Overall I think people spend more time writing error checks than they should, and at the same time there are still too many mis-handled errors. We would like to see if we can find a way to make it both less work and more correct. Maybe we can, maybe we can't. The process is slow and needs to backtrack out of formerly-promising dead ends, and that's OK.

There's a very large amount of conversation right now around try, and that's good. Like I said in the tweets, community-wide discussions like this one are open source at its best.


I think "try" is a terrible name, because it overloads try/catch which is very common usage these days but it's nothing like that. It's basically a error checking macro, so give it a new name.


Rust had the try! macro before which is similar, the name is fine I think.


> Overall I think people spend more time writing error checks than they should, and at the same time there are still too many mis-handled errors.

Care to elaborate on both points, or direct me somewhere I can learn more about these opinions? Thanks!


Why not allow call-by-name parameters to functions? That would enable library authors to build whatever error handling conveniences they wanted. This feature dates all the way back to ALGOL-60.


I think you're answering your own question there:

> That would enable library authors to build whatever error handling conveniences they wanted

What I want from a language - especially if it's, like Go, aimed at really big project - is for it to be opinionated and narrowed in options. That's Bill Kennedy's point as well, they're adding a language level feature so now there's one more way to do a very common thing. And when there's multiple ways to do something, they will all get used.

"But what about conventions and code reviews?", you might ask; that's cognitive overload that, if there was only one way, would never be a thing. I mean another feature of Go is that it enforces a certain standard (with tooling and formatters), so that as a development team you should never have to waste time and energy on discussing or arguing about formatting.

I'm not against changing how errors are handled now, as long as they're standardized and the One Way to do things.


I respect this position, but we must also recognise that it makes Go less of a general purpose language (as is often claimed) and more a domain-specific language (with the domain being systems/network programming).


Just curious, why a twitter thread response and not a blog post?


> Rob Pike gave a talk last year at the Go Sydney meetup where he talked about the Go 2 changes. In his talk, he suggested that the use of if err != nil in code bases is nowhere near as common as the vocal minority suggest.

This page shows the most common words found in a large corpus of open source Go code on GitHub:

https://anvaka.github.io/common-words/#?lang=go

The top four most common words are "err", "if", "return", "nil". These are each more common than "func" and "string" combined. It's hard to square Pike's comment with that evidence.

I don't know if the try proposal is a good one or not, but I think it's pretty clear that Go's original approach to error handling is quite verbose. Whether it should be that verbose is an interesting question, but personally I think making the user repeat that exact same boilerplate every time adds little value. If you want code that can fail to be visible, at least make the boilerplate shorter, which I guess the "try" proposal accomplishes.


> The top four most common words are "err", "if", "return", "nil". These are each more common than "func" and "string" combined. It's hard to square Pike's comment with that evidence.

Each of those words are extremely common in Go code, even outside of the 'if err != nil { return err }` construct.

It makes sense they'd be more common than 'func' and 'string' because functions usually contain at least one if statement and return statement, and err is probably the most common variable name, and nil is used to represent the zero value of many different types. The 'string' word is only really used in struct fields, function signatures, and some variable declarations, but usually the string type is inferred on assignment.


you can click on the words and `if err != nil {` is the most common pattern in each with `return err` being second place (both by a large margin)

Rob Pike is, very likely, using a different set of programs to judge.


I feel like what you say is tangential. Op didn't say that it isn't logical to see them as most used words. He says that pike's opinion doesn't seem to hold a lot in the light of this evidence.


Go's approach to a lot of things is quite verbose.

Every editor has snippets, or the equivalent, making boilerplate simple to write. And you adapt to reading it pretty quickly - it just becomes punctuation after a while, and you only notice it if it does something unusual.

which is my problem with "try", and why I won't be using it if it gets accepted. It breaks the rhythm of "do something, check for errors, do something, check for errors" that Go code has, and that becomes easy to read.


Just goes to show how out of touch with established programming language research and practice the golang authors are. Now they're re-inventing exceptions, but badly.

It seems a lot of things in golang were done just for the sake of being different, with nothing to back it up.


"Please don't post shallow dismissals, especially of other people's work. A good critical comment teaches us something."

https://news.ycombinator.com/newsguidelines.html


> It seems a lot of things in golang were done just for the sake of being different, with nothing to back it up.

Take an afternoon to go through the Go FAQ [0] (they answer why they don't have exceptions), design documents [1], including this fairly recent one [2] about error handling - they explicitly discuss exceptions and the pros / cons - and that's just what ended up in the design documents / FAQ, there's been at least ten years worth of discussion gone into that beforehand. Do some googling before making accusations like that please.

[0]: https://golang.org/doc/faq#exceptions [1]: https://github.com/golang/go/wiki/DesignDocuments, https://github.com/golang/proposal [2]: https://go.googlesource.com/proposal/+/master/design/go2draf...


I'm aware of their rationales, what I'm saying is that they don't make sense and are dismissive, and have no studies at all to back them up.

It's there in the first link you posted for example, "we believe exceptions lead to convoluted code". Belief here is not sufficient, it must be conclusively shown that it's otherwise, especially given that subjectively speaking it is possible in golang for errors to slip and not be handled at all (I saw it in real code bases), much much worse than any "convoluted" code that exceptions may result in (again it's very subjective - the way golang handles errors results in much longer code that's harder to follow).


"try" as proposed here has nothing to do with exceptions as other languages implement them.

whether that's a good thing itself is another conversation


Not only is your belittling of Go authors gratuitously offensive but it is uninformed as well. That is just one of the many proposals that Go received along the years and there's no intent to implement it at the moment.


I'd say it's uninformed for a completely different reason. Look at the bios of the Go authors. "Out of touch with established programming language research"? Seriously?

"Making different choices than you would have" != "out of touch with research" - especially when they know the research better than you do!


> Look at the bios of the Go authors.

Appeal to authority fallacy. Furthermore, this was evident several times when discussions about things like generics came up. I think they even admitted it at some point.


> Just goes to show how out of touch with established programming language research and practice the golang authors are.

When you said that, was also the appeal to authority fallacy.


Which authority? I observed other languages like Kotlin, Scala, Rust, D, C#, Java, C++ and saw what they have in terms of program design and features.


"Established programming language research". That's a set of authorities, even if you didn't name them.

Kotlin, Scala, Rust, D, C#, Java, and C++ are not language research. They're existing practice.


It's more like an appeal to a consensus if you will, since those languages converged on a set of good established practices (that came out of research obviously) that have actually shown to make a positive difference (e.g. Rust, Scala, Kotlin, and soon Java and C#, have sum types, and pattern matching), and the way they deal with errors (Result type in Rust - where the compiler actually forces you to deal with errors unlike golang, and exceptions in the rest, which again, don't let errors slip through accidentally unlike golang).

The golang authors threw away all that (and are still dismissive of things like sum types) because of, well nothing really to back it up. The same reason they have null pointers whereas all new languages moved away from them, and some like C# are being retrofitted to address them. I think the rationale they gave to having null pointers is that "they're part of the underlying machine" or some ridiculous reason like that, which further proves my point that they're out of touch with established programming research and practices.


> The golang authors threw away all that (and are still dismissive of things like sum types) because of, well nothing really to back it up.

Because of multiple decades at Bell Labs, which was the computer science research facility at the time. And because of multiple decades of experience using many languages to write a huge amount of real-world software. And because of specific problems that they found with using C++ in their specific environment.

Look, you don't think go was the right way to go. That's fine. You even base it on having used go and not liking it. It's good that you're basing your opposition on experience, not just theory. But don't claim that the go authors are ignorant of programming language theory just because of the direction they chose. They are not ignorant at all, and they chose the direction they did with full knowledge of what others were doing. It may turn out to be a mistake, but it wasn't from ignorance.


> And because of multiple decades of experience using many languages to write a huge amount of real-world software.

I'm genuinely interested to see the real-world software you're talking about. Other than UTF-8 (and that's mainly a spec), what's there to show? No, Plan9 is barely used. Most of their work was in C either way.

> And because of specific problems that they found with using C++ in their specific environment.

They found problems with C++, sure, so they made a language that addresses problems with C++ that other languages already did, without looking at what said languages did (e.g. Pascal - and even Java and C# - for fast compile times, and C# and ML-languages for generics). Again, they admitted that they were narrow sighted and mainly looked at C++, not what other languages that actually addressed issues with C++.

The golang authors are not language designers, that's always something to keep in mind whenever looking at what (mostly bad) decisions are being made in the language. The issue is, people are just looking at the brand behind the language and are not thinking objectively, leading to more unmaintainable code bases to deal with. Perhaps it's a good thing for software engineers though, who are going to be paid to clean up this mess :)


Unrelated, but why on earth is "iNdEx" used almost a million times? Is there a joke that I'm missing at play here?


It seems it's generated by the protoc compiler. E. g. https://github.com/weaveworks/common/blob/80ff0769bb38e575cd...


It's funny but this reminds me that my first 'wtf C, you are dumb' moment was trying to use a variable name 'index' in some code in 1989, and getting completely incomprehensible errors in return.


Often when we talk about verbosity in a language, we mean something is needlessly descriptive in a way that can make things tedious or hard to maintain.

Just yesterday I made the joke that you can recognise a Java developer at a party because they start almost every sentence with "public static void". Ha. See, funny?

Go's verbosity feels different, and I don't think it's "verbose". It's clear. It's about making it really clear what is happening.

What surprises me about the proposal is that they think that go's error handling does not reduce the number of errors, because in my experience it does in fact reduce the incidence of run-time errors significantly, and also makes it very clear how to maintain error handling code.

I'm not sure I like the try proposal, but I'm not sure I hate it. What I do know is that the verbosity you're complaining about doesn't feel to me like the same kind of verbosity I complain about in other languages, somehow.


> It's about making it really clear what is happening. You could say the same thing about Java, so your argument basically boils down into “I like Go, and I don’t like Java”. What is clear and important to one person is needlessly verbose to another, what is cryptic to a newcomer might be beautiful and concise to an expert. I’ve encountered idioms that horrified me the first time I saw them which I eventually grew to love, and I’ve been smitten by syntax sugar that I thought would be great and turned out to be a disaster. It’s hard to know how stuff like this will play out, every language evolves its own style and rhythm over time, and sometimes it takes reading and writing a lot of code to find the right balance.


These words are commonly used individually, but that says nothing about how often they appear in a particular order.


have you tried clicking on that link?


I agree with blog author's sentiments. Go is a bit too imperative but that really aides in a team environment. Though the Golang's constructs are a bit bland (especially error handling) it actively prevents code to be too intelligent there by aiding readability I am able to work on the code I wrote 2 years ago with little difficulty in Golang and I can't make the same claim for other `leading` languages (though I assume full responsibility for what happened)

Edit: grammer and spelling


Before commenting, please read the detailed design document [1] and see the discussion summary as of June 6 [2], the summary as of June 10 [3], and specifically the advice on staying focussed [4]. Your question or suggestion may have already been answered or made. Thanks — ATT: Robert Griesemer

---

I like writing “if err != nil { … }” so while I disagree with the proposal to add the error check function “try()” I will keep an open mind because I do not have the technical literacy as @griesemer or other people in the Go community to propose something better. I’ll experiment with “try” a few times in personal projects, but I can see myself writing “if err != nil” for several years unless I find a more compelling reason to change, same reason why my team and I haven’t adopted “go mod” despite having Go v1.12.6 (latest stable version as of today) in all our production servers.

[1] https://github.com/golang/proposal/blob/master/design/32437-...

[2] https://github.com/golang/go/issues/32437#issuecomment-49926...

[3] https://github.com/golang/go/issues/32437#issuecomment-50061...

[4] https://github.com/golang/go/issues/32437#issuecomment-50187...


> I like writing “if err != nil { … }”

That idiom was my biggest turn off in learning Go.

I would be 100x happier with "if not err {...}"


Bill helped me with my resume once in the Go slack channel. Great guy. I also happen to agree wholeheartedly that introducing try would be disastrous to the mostly consistent codebases Go is currently able to painlessly produce.

Personally I don't want error handling changed at all. It's very repetitive in it's current state, but clearer than in any other language I've used.


Eh, I feel like Go errors are a mess, personally. Though, I'm not sure how to solve them. Mind you, this is after ~5 years of use, so it's not from "an outsider" - I hope, at least lol.

Most of blame for the mess is that the error interface throws away meaningful information. Then the caller is left mentally checking how to handle the error. Do I value check? Type cast? String check? Does the library expose a function to check, or do I do it myself? Can I get stack traces in some cases without introducing a production cost?

I'm not sure if Go needs a different type of error interface (ie diff methods), or if it needs Generics to solve this properly in my view. But, I just know I've hated the experience.

After I switched to Rust it all felt so familiar, and yet so different. I believe Rust and Go share error patterns. Yet in Rust I'm never left wondering what an error is, what I should do with it, how I check for specific types. This is largely due to awesome enum support and of course generics. Does Go need this feature set to "fix" my complaints with errors? Probably not.. but I do think it needs something.

Though I will agree, I don't think the `if err != nil` is fundamentally the problem with Go's errors, at all. Sure, the UX could be improved, but that's another story all together imo.


There is an ‘error inspection’ proposal for Go 2.0 which will hopefully solve this issue: https://go.googlesource.com/proposal/+/master/design/go2draf...

The proposal has been partially accepted and implemented in Go 1.13: https://github.com/golang/go/issues/29934#issuecomment-48968...


Honest question as a non Go user: how would generics (from a C# perspective) help? I assume that Go generics would be analogous those in C#.

Edit: spelling, genetics to generics.


Rust has a wrapper type `Result<ReturnType, ErrorType>`[0] that essentially works the same was as Go's `return value, err` idiom, but is safer because the type is defined so that you can't access the returned value unless you also handle the error condition, and it can have a convenience syntax for chaining since it is a dedicated unambiguous type. The compiler also specifically warns you about unused `Result`s, to avoid unhandled errors, and the API is structured so that nonsensical states such as `(nil, nil)` or `(value (!= nil), error (!= nil))` are impossible (unless specifically opted into).

Without generics you would have to either throw away the type information, reimplement a `Result` type for every value type, or copy/paste the implementation at each usage site (the common Go pattern).

Go's support for multiple returns itself is also a hack around the lack of support for tuples (which would be trivial to implement if you had generics).

[0]: https://doc.rust-lang.org/std/result/enum.Result.html


That reminds me of Scala's `Either` type, which I'm sure is also present in other languages (and more functional circles which I'm not familiar with at all).

I think the important part there - and the contrast with e.g. exceptions - is that an error is treated as normal, as one possible outcome of calling a method, instead of exceptional as exception-based languages seem to be. I mean yeah of course a file can not be available to open, that can be expected. (That is the simple use case of course, I'm sure there's much more complicated situations).


> a hack around the lack of support for tuples (which would be trivial to implement if you had generics)

To a first approximation, the purpose of Go is to stop every codebase from reinventing its own tuples or Result<> type.

Go does have generics, the Go compiler could add a chaining syntax for trailing err interfaces (like rust ? / try!) without needing to allow user-defined generic types.


> To a first approximation, the purpose of Go is to stop every codebase from reinventing its own tuples or Result<> type.

Rust has this too. You can throw away type information and incur a runtime cost just like with Rust by using "Trait Objects", which are Rusts version of interfaces, basically. Luckily most choose not to use this for error communication.

However I struggle to understand that throwing type information away and incurring runtime cost is the pinnacle of error communication.

Furthermore, the idea that this somehow prevents codebases from "reinventing" anything is absolute nonsense. Basically every Go lib out there has different error handling. Hell, I wrote a library of my own (similar to pkg/errors). The very items I listed in my OP showed how every lib has it. You have no idea how each Go lib reinvented the wheel to know how you handle their errors. All with runtime checking. Madness.


I hope it's not literally to prevent people reinventing these simple data structures.

The ecosystem exerts a lot of pressure on developers to not reinvent fundamental things, in Haskell I practically never reach for a StrictTuple or StrictEither even when I'd like strictness specifically because they are clunkier and less well integrated with other libraries.

Scala is the only language that comes to mind with this issue (scalaz vs Cats) and my impression is that even they have mostly converged.

I would say Go has much stronger idioms than either of these languages, and wouldn't need to be afraid of people inventing their own fundamental types provided they're included in the standard library.


> Scala is the only language that comes to mind with [the issue of needlessly reinvented fundamentals]

C/C++ is another example. There are a plethora of string types mostly tied to which base set of libraries you're using. If you're using the C++ stdlib, you have std::string. But if you're using Qt, you're using QByteArray and QString instead. And probably similar for GLib/GTK.

(Does std::string even have any encoding support yet? When I last used it, it was just a string of bytes without any encoding awareness.)


> so it's not from "an outsider" - I hope, at least lol.

I used golang at an employer and you're right, it's a huge mess (and error handling is just one part of it).


It's a shame too. The language is actually quite nice. They made a lot of decisions that simplify and improve code quality, large code bases, etc.

However a few decisions are huge signals that they didn't really think through them. Error handling and the now-solved package management are two big ones.


> They made a lot of decisions that simplify and improve code quality, large code bases, etc.

I keep seeing those claims (e.g. "programming in the large"), but in my experience, practically all the decisions in the language make it more awkward and difficult, even unnecessarily so, to deal with large code bases.

golang's interfaces for instance, are optimized for a small use case, at the expense of them being useless for actually useful things, like tagging and fast code search. The way "methods" are assigned to structs is overly verbose, and makes it easy to have to jump around even several files to figure out all functions that are tied to a struct. The list is too large to list here.


So, in the large codebases I've been a part of I think Go makes it easier.. BUT, it's an asterisk "easier*". The asterisk being I think it's easier to reason about Go projects, large and small, in a "local sense" only. That is to say, I think you can wander around Go code with less context on the whole project and have a better idea on what that tiny piece of code is doing.

Compared to my Rust code bases, I feel like you need a larger picture of the project to understand what smaller code pieces do.

Is that a good thing though? Not sure, honestly. Should I really feel I understand what a function does, without understanding the functions role on the project itself? How can I truly understand a function without understanding its role?

Go feels like it's easier to understand the implementation, but not the context/purpose. Arguably, this may not be good.

I still prefer Rust, fwiw.


> That is to say, I think you can wander around Go code with less context on the whole project and have a better idea on what that tiny piece of code is doing.

Again, what specifically about golang that makes it that way, other than errors vs. exceptions (keeping in mind that any line in golang can panic)? And what's preventing you from writing similar code in another language? I used Scala before and we had Option and Result types, and treated exceptions as panics. The same applies to Rust.

I found that because of golang's verbosity, it actually made it harder to understand what the entire project was doing. e.g. code bases I worked with were littered with one off functions that amounted to what basically was a map or map+filter, where this would have been a one liner in any modern language, including Java, C#, Kotlin, etc.

Basically, I'm agreeing with the rest of your comment. I find that a "denser" language (to a certain extent) allows you to understand the overall project quicker. There's probably a sweet spot somewhere, and I feel that languages like Java and C# are quite close to it. Keeping in mind that you want a high level overview of a project when you start off anyway, there's no magic solution that will give you that.

It's just that I keep seeing the same claims repeated so many times, and it really seems that they're just being repeated without any basis at all, and repeated just because what the golang author's claimed. So far, almost everything I've seen has been hype.


I really think how you are using the language had a large effect on how you are going to view all this. If you are building a commandline utility then the way go deals with errors is great you just panic and send back a message. If you are building a long running web service you want a whole lot more info to go into your logs for when things go wrong. I dunno what the solution is but try/catch doesn’t seem to be the right thing to focus on.


Despite the name, there's really nothing in common with try/catch. It's just sugar for if err != nil { return }.


Try is a terrible choice for a name exactly because of try/catch, I don't know why they decided on that.


Personally I find that go’s error handeling adds a lot of noise that hides what the code is doing. Basically you have to mentally filter out a whole lot of the “if err == nil” before you can work out that bit of code’s functionality.

Also computing is all about eliminating or automating repeaditve tasks. having to write out (almost) the same code after almost every function call goes against the core usefulness of computing.


How you handle errors is just as important (if more more) as the rest of the code.


There's a difference between handling and propagating errors, most Go-code I've come across/written is pretty much using exceptions, but with 10x the noise and none of the convenience. Which might be fine, different languages have different goals; maybe simplicity is more important. But pretending it's somehow superior isn't fooling anyone.


An exception interrupts the entire flow of the program.

In go if something is truly exceptional then you can panic, but most of the time an error is non-fatal outside of a particular request (request not just related to a web request).

I agree, many people do errors incorrectly, but `try` does not make them suddenly correct either.

It's quite healthy for the community to have this discussion because this is introducing not just a new built-in function/expressiony thing, but also a means of flow control.


Unconditionally returning on errors all the way has exactly the same effect. Arguing that it's wrong isn't very constructive given that everyone seems to be doing it.

panic() is just another symptom of the same disease.

Exceptions are a very useful tool, and so is pattern matching and support for multiple returns. There is no one true way to handle errors.


> Unconditionally returning on errors all the way has exactly the same effect.

No, an uncaught exception crashes the program. I guess you can generically catch all exceptions and get the same affect.

> panic() is just another symptom of the same disease.

Not sure what you mean here

> There is no one true way to handle errors.

Never said there was. I only made a comment that `try` does not fix how people are handling errors.


Sure it is important but I have had functions looked like this

   func foo() error {
      a, err := bar()
      if err != nil {
         return err
      }

      b, err := baz(a)
      if err != nil {
         return err
      }
     ...
    }
In some code this error handeling is 3/4 of the function lines. Sure the error behavior is important but is it 3 times more important then the logic of what you are writing?

The if nill construct contributes to cognitive load and detracts from code plainly tells you what it does. And speaking from a practical perspective I’m going to be far more productive if 3 lines of commonly used code is turned into five characters.


Yes, that's how Go code looks, it has a rhythm of "do something, check for errors, do something, check for errors". This is a great idea, because checking for errors is explicit, and dealing with them is immediate.

After a while of working in Go, you get used to the rhythm and you stop noticing the "check for errors" part unless it does something different. You also notice when the check is missing.

And having most of your code devoted to error handling makes total sense. There are usually lots of errors states and only one "golden path" state - properly handling all the possible errors your code could generate will mean writing more error-handling code than logic code because there are more error states than non-error states it can get into.


All “try” does is allow you to write those three lines as five characters. In natural languages the more frequent used a word is the shorter that word tends to be. “Check for errors and return” is something that is frequently said in go, following from those principles it should be something that it short to write.


I see that, but it breaks the rhythm, and introduces an alternative syntax that makes it harder, not easier, to read. And code should be easier to read than to write, because we're going to read it way more than we write it.

Instead of "do something, check for errors, do something, check for errors" we would have "do something, check for errors, try something, do something, check for errors" (assuming not all errors are going to be "try"d, and some will need to be wrapped).

It stops being obvious when I've missed an error-check. That's bad.


How can you miss an error check?

Either you have used try and therefor there has been a check for errors.

Or you have not used try and therefor you had to assign to err and therefor you have to use a check for errors.

Any assignment to err indicates you need to check for errors just like it is currently.


Been using Go for ~4 years and I agree - although I would like to see more documentation about scaling error handling in production codebases however.

Controversially I do support generics in go2.


> biased because only those who had a problem submitted proposals and this leaves out all of the developers who don’t consider error handling in Go needs to be better

It's probably also biased due to the people who gave up on Go completely because they saw it as a sufficiently major issue and/or had little faith in Go ever improving (I can't exactly put a finger on it, but a lot of the decisions in Go seem to be backed by "... and if you don't like our approach, you're WRONG").


> "if you don't like our approach, you're WRONG"

This was also my experience interacting with the Golang team while working at Google.


> a lot of the decisions in Go seem to be backed by "... and if you don't like our approach, you're WRONG"

I don't understand this complain. Ultimately that's how most if not all programming languages are developed. A few individuals act as guide and gatekeepers and that's fine.


I don't write a lot of Go but I have fuzzed a reasonable amount of it. One of the most common types of bugs I encounter are slice out-of-bounds errors. In Go, this invariably leads to immediate program termination (panic). Especially parsers of serialized data received over the wire (like protobufs) are susceptible to slice OOB bugs because the programmer needs to reason about lengths, offsets and integer wraparounds, and this is often hard to get exactly right. Due to the combination of parsing untrusted data, the likelihood of bugs and flat-out crashes if there is a bug, network-facing Go software is especially prone to very low-effort denial-of-service attacks.

Python and Java got this right: everything throws an exception, including out-of-bounds access (IndexError, ArrayIndexOutOfBounds), so by wrapping your whole application in a try/except block you can eliminate a whole class of otherwise fatal errors.

Unless I missed something, the upcoming change isn't going to alter the handling of OOB's, which seems like a missed opportunity because a fully-fledged try/catch mechanism could effectively defuse many bugs.


Go already lets you recover from panics in a way similar to Java exceptions. For example, the standard library's HTTP server recovers from panics so if a request handler has an out-of-bound array access (or other panic-inducing error) the server continues serving other requests. It sounds like the programs you're fuzzing are missing an opportunity to be more robust.

The "try" proposal is about handling routine, expected errors.


In Java land checked exceptions are considered to be an anti pattern for sometime. If I remember correctly one of the major change in Hibernate ORM 3.0 was converting checked exceptions to runtime equivalents (ie, caller is free to disregard the exception) (this happened 10-12 yrs ago). Coming back to Golang, you get the same behaviour if you are not checking `error is nil` condition


When Java finally got generics, it didn't add any support for generic sum types of checked exceptions.

  stream.map(f).collect(…)
should obviously be able to throw anything f could throw. But instead f is forced to wrap every checked exception, and so pretty much everyone has given up and started declaring new exceptions as unchecked.


Not sure what you mean by "free to disregard", but the exception being unchecked doesn't fundamentally alter the control flow of a thrown exception. It only affects type checking - hence the name.


> In Java land checked exceptions are considered to be an anti pattern for sometime.

It depends on whom you ask. Some people quite like checked exceptions. Either way, both checked and unchecked exceptions are superior to golang's approach to error handling.


I find the logic the article uses to make its point a bit handwavy, or you could even say "wrong". If the language specification required the first line to start FIRST and then every subsequent line in each file start with AND THEN, so that this:

   package main
   
   import (
    "fmt"
    "net/http"
   )
   
   func main() {
    http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
     fmt.Fprintf(w, "Hello, you've requested: %s\n", r.URL.Path)
    })
   
    http.ListenAndServe(":80", nil)
   }
becomes

   FIRST package main
   AND THEN 
   AND THEN import (
   AND THEN  "fmt"
   AND THEN  "net/http"
   AND THEN )
   AND THEN 
   AND THEN func main() {
   AND THEN  http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
   AND THEN   fmt.Fprintf(w, "Hello, you've requested: %s\n", r.URL.Path)
   AND THEN  })
   AND THEN 
   AND THEN  http.ListenAndServe(":80", nil)
   AND THEN }

there is a very good chance it would not make it into "the biggest challenge you personally face using Go today." Because it's not a challenge. It's stupid, but not a challenge.

So just because the verbose incantation

   if err != nil {
       return err
   }
or other issues, could be thought of as very similar to AND THEN, it doesn't mean that this is the biggest challenge someone personally faces. it could be stupid, but you get used to it.

so it depends on the wording of the question. Basically the real question isn't how big of a pain point it is. It's how much it could be fixed.

my specification for the formatting of the language would be hugely improved by removing the AND THEN line from each line of the code. It's kind of ugly.


It would also probably not come up in surveys, because the only people using the language would be the ones who are OK with it - others would simply take a look at it, conclude that having a pointless "AND THEN" everywhere is just too ugly, and move to a language that made better choices.


I've heard people complain about the explicit error handling in go, but that is mostly because they are coming from other languages such as java which have inferior error handling that lets them write 'neater' code, but which generally results in less than robust error handling.

Picking on Java, it has checked exceptions, unchecked exceptions, and errors. It's basically a minefield of complexity leading to undefined behavior and/or misbehaving error legs. I can't tell you how many times I've seen InterruptedException mishandled, Throwable (which includes VirtualMachineError) caught with no appropriate handling (quick - what is the appropriate handling of a VirtualMachineError?), or exceptions allowed to wildly propagate.


> java which have inferior error handling that lets them write 'neater' code, but which generally results in less than robust error handling

Wowza, that's a strong stance.

Look, I'm a fan of Go and Java is definitely aged in a lot of places but this is one of the most bizarre claims I've seen. Criticism of Java's exceptions come from both sides, some people who hate unchecked exceptions so "anything can throw" (same reason C++ camp often avoids exceptions); or that checked exceptions are stupid since you can't meaningfully fix Interruptions or IOExceptions so it's verbose and meaningless to force local catches; they almost always need a rethrow. Both of these criticism are fair because in the end they both make sense sometimes and thus are misused for one another occasionally. We'd all like one answer consistently applied but we don't have it here.

Error codes formalized as a part of an API have their place but I find their utility is much more niche than people give them credit for. Why should I decode error codes when a String and a Trace will do? Hint: only when errors have formal semantics such as within a protocol. How I handle the error is partially decided by spec.

But to say that in comparison to Go? Go has its normal err mechanism and panic/recover. err is a huge step up from C style errno/manual checking. In practice, Go's err is embarrassingly used just like a checked exceptions: observe and ignore/log or propagate. You end up just unwinding the stack manually, losing the trace, maybe adding context occasionally, like 70% of the time. You handle it where it's meant to eventually, sure.

Like everybody does, when it is done right.

panic/recover is just unchecked exceptions all over again anyway. They seem to be treated as voodoo idiomatically, sure, but they're there.

Go is a great language that brought some really good language runtime features together in a pretty tidy package. Besides maybe defer, which is pretty clean, error handling just isn't a part of the language that got the polish that other parts of the language got on the first pass.


> which have inferior error handling that lets them write 'neater' code, but which generally results in less than robust error handling.

Error handling in languages with exceptions is (1) more robust, as you actually have to make an effort and explicitly ignore exceptions, and not just accidentally forget to handle them or overwrite them as with golang. (2) you don't have to incur a branch for every error check, which should result in faster code in the non-exceptional case. (3) the code reads easier and clearer, especially where you have to chain calls, which in golang will take up much more code. (4) Exceptions are concrete types, and give you a stack trace automatically, unlike golang where errors are mostly strings and if you need any context you have to manually populate it.


This blog pretty well sums up my feelings about the try method. It just doesn’t really seem necessary. I’m sure it would help in some of my code, but importantly not all of my code, and if a construct is only available some of the time, I will inevitably favor the doing the same way all of the time rather than adding a little syntactic sugar every now and then.

I’d much rather see some kind of genetics support get well integrated, given that’s a problem I run into fairly often.


Agreed! (Though I’m assuming you meant to type generics. However, I’d love a library to alter genetics)


Hahah this is what I get for typing on my phone.


Go errors prioritize explicitness, in declaration of what functions might fail, and in handling of those failures.

It seems like a mistake to add a language feature that works against explicit handling. At minimum, the endorsement of returning the raw error reduces clarity on what "good error handling code" should look like.


agree totally.

I'd prefer to see an official errors.Wrap function instead of "try", for the reasons you cite.

(shout out to Dave Cheney's excellent "pkg/errors" implementation of error wrapping)


> effort to understand exactly what those 5% of Go developers meant when they said they wanted improved error handling

> If you look closely, error handling is not even in the top 3 challenges faced by developers, it’s number 5

Nonono, that's not what those numbers mean. 5% said it was their absolute biggest problem. For all you know it could be the #2 problem of 90%.


This will be legal with the proposed `try` statement [0]:

  info := try(try(os.Open(file)).Stat())
I'm worried about nested versions of this and having to unpack them when reading code, which at the current moment has a much easier imperative block structure.

[0] https://github.com/golang/proposal/blob/master/design/32437-...


You're right, that's horrible. However, you can also write it like this:

  f := try(os.Open(file))
  info := try(f.Stat())
This can be further improved by applying special syntax highlighting that makes the try less prominent.

Meanwhile, the cleanest way to write it right now seems to be:

  f, err := os.Open(file)
  if err == nil {
    return err
  }

  info, err := f.Stat()
  if err == nil {
    return err
  }
That's 8 lines of code to do the same thing. Did you spot the bug I introduced, or did you miss it because the volume of code made you skim? (error check has == instead of !=)?

If you want to judge code by the worst thing you can do, you'd need to compare with things like:

  var info os.FileInfo
  if f, err := os.Open(file); err != nil {
    info, err = f.Stat()
  }
  if err != nil {
    return err
  }
(Did you spot the bug? If err is already declared somewhere further up, this will compile <https://play.golang.org/p/0l-SsEbKQh2>, but the "err" within the if shadows the previously declared one, so the errors from os.Open and f.Stat are never checked.)


> error check has == instead of !=

And you return the error as-is if its nil; seems fine to me?

Your second example is also a bit... far-fetched because it tries to be smart by putting the error checking in the same line. There's also the "line of sight" guideline which states that the happy path should be left-aligned, while edge cases - like errors - should be in indentation [0]. See also the law of least astonishment [1], which your final example does not conform to.

Yes it will compile and yes it is an issue but that's not because of the language. It's because of an attempt at cleverness by writing more compact, of having the happy path in an if, and (arguably) by reusing variable names - but this seems pretty common, and longer variable names like idk, `osOpenError` and `fileStatError` seem to be discouraged in favor of reuse.

So I'm not sure if you're getting your point across like this.

[0] https://medium.com/@matryer/line-of-sight-in-code-186dd7cdea... [1] https://en.wikipedia.org/wiki/Principle_of_least_astonishmen...


I've never seen this come up as an issue when reading Rust, which has the same feature being proposed for Go (and then later shortened it to just a single character, ?).


? is a big improvement over try specifically because you don't have to unpack nested statements. Everything flows left to right.


Maybe I am caught up in the "not familiar is hard" look of it and will come around. You are most likely correct :)


I was never a fan of try. It's too magical and breaks the flow.

Far better to use something more explicit and controllable:

    f, err := os.Open(filename) onerr return 0, err
which is just syntactic sugar for:

    f, err := os.Open(filename)
    if err != nil {
        return 0, err
    }
also:

    err := DoSomething() onerr {
        fmt.PrintF("We got error %v\n", err)
        return 0, err
    }


The way errors are handled in go feels like the people were thinking hard "let's not repeat the horror of C++ exceptions" and are ok with ugly warts so long as they're not uglier than exceptions...

It's clear that there's way too much boilerplate in go procedures that deal with error handling by systematically bailing and passing the failure upstack...

But the most confusing thing to me about try() is that, although it's shaped like a function, it actually returns from the current scope on error. It's dangerous syntactic sugar IMO to make this one function-call-like feature not behave like an actual function call...


Too late, Go already has panic(), which is a magical function that unwinds the stack.


!

So it's legal to write something like...

   panic(panic())
or

   a := try(try(f()))
...?


panic() doesn't have a return value, so no. But try() has.


I wonder if Go just needs syntactic sugar that's similar to the RETURN_IF_ERROR that's common in Google C++ code (for example, in protobuf: https://github.com/protocolbuffers/protobuf/blob/master/src/...)


Does anyone have a link to the "try" proposal the author is advocating against? It's not clearly linked in the article.




Try aka ASSIGN_OR_RETURN


All the try(), catch(), and onerr proposal are ugly patchworks. All I need is to be able to skip the != nil and just be able to write: if err { ... }


I'm glad this post (and others) are sounding the alarm.

Go's greatness could very easily be destroyed by a small number of missteps like this one.

I'm wishing for the absolute rule of the curmudgeony quadrumvirate like the one that designed Go. This feature-bike-shedding hungry mob is proof of the problem with democracies.

And when it comes to programming languages (unlike societies) the the trade-offs for a democracy are not worth the price. Go should (almost) completely ignore the crowd on design issues. It should have changes made that a small cabal of experts unanimously agree with.


This proposal really seem to rile up the community, can't it be shelved and revised until let's say v2.4 or something?


Of course it can. It can also be shelved indefinitely or not done, but at some point, it's time to make a decision. Opponents of this would of course prefer it to be "shelved" instead.


I wouldn't like to see a change to error handling, I would like to see generics added (in the form that was described in the Sydney talk).

Maybe a stack trace would be nice in the errors, though


Stop fighting it embrace it. Error checking in go is one of my fav part of go.


I am not a Go programmer, but:

The article suggests that try() isn’t solving a big problem. If I were a Go programmer, I think I would want a bigger change, specifically, a proper sum type for errors. In Go, the convention is to return a result and an error despite the fact that essentially every function that can return an error wants to return a result or an error. This results in complexities in the proposal about what the result should be when try() causes an error return. The sensible answer is that there should not be a result.

The way that Rust (and Haskell and many other languages) do so much better than Go has little to do with fancy ? operators. It’s that the return type makes sense.

Of course, sum types without genetics would be odd, but I don’t see how this would stop Go — a special case specifically for “such-and-such type or error” seems consistent with the Go typing philosophy.


I think it's funny that you mention a bigger change but sum-types are 80% of what Go already has here. While it's true that it solves the ambiguity issue of representing `Either Result or an Error But Not Both` instead of the `Result or Error or Both`; that doesn't solve the issue that anyone actually has, which is that most errors are handled with

  if err != Nil {
    return err;
  } 
after every other line sometimes. It's verbose, manual, and doing the helpful thing like adding context for the error is repetitive and often omitted. As I've mentioned elsewhere, you essentially want an exception system.

Some care still needs to be taken with how other language features interact with it. You'll probably want a sum-type-y way to catch exceptions as an expression eventually. What happens if exceptions are thrown from functions in defers, etc. But unlike more FP languages where error propagation is much easier in a soup of composed functions, Go is much more Java/C#-like. Most functions have much more important names and meaning which make stacktraces much more valuable.




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

Search: