golang / go

The Go programming language
https://go.dev
BSD 3-Clause "New" or "Revised" License
123.92k stars 17.65k forks source link

proposal: leave "if err != nil" alone? #32825

Closed miekg closed 5 years ago

miekg commented 5 years ago

The Go2 proposal #32437 adds new syntax to the language to make the if err != nil { return ... } boilerplate less cumbersome.

There are various alternative proposals: #32804 and #32811 as the original one is not universally loved.

To throw another alternative in the mix: Why not keep it as is?

I've come to like the explicit nature of the if err != nil construct and as such I don't understand why we need new syntax for this. Is it really that bad?

ghost commented 5 years ago

I like the error checking as-is, but if this is truly an issue that must be solved, I think a new keyword is probably a better solution. Any solution will likely involve control flow changes, and it's best to make that as obvious as possible.

In this example, the on condition is evaluated every time err is set.

func example() (foo int, err error) {
    on err != nil {
        return foo, err
    }

    foo, err = calcThis()
    foo, err = calcThat(foo)

    return
}

This works without declaring names for return values in the function signature, as well.

func example() (*int, error) {
    var err error

    on err != nil {
        return nil, err
    }

    foo, err = calcThis()
    foo, err = calcThat(&foo)

    return &foo, nil
}

This could also be set multiple times. Here's a contrived example:

func example() (*int, error) {
    var err error

    on err != nil {
        return nil, err
    }

    foo, err = calcThis()

    on err != nil {
        return &foo, err
    }

    foo, err = calcThat(&foo)

    return
}

To my eyes, this retains Go's style and readability, while making it clear what is happening at each step (once you understand the paradigm), since it is effectively inserting that condition after each occurrence of err being set.

You could even do the following:

func example() (foo int, err error) {
    var message string

    on err != nil {
        return foo, errors.Wrap(err, message)
    }

    message = "failed to calc this"
    foo, err = calcThis()

    message = "failed to calc that"
    foo, err = calcThat(foo)

    return
}

Finally, this has applicability beyond error handling. Want to return if foo == 0? Another contrived example:

func example(i int) bool {
    on x == 0 {
        return false
    }

    x = calcSomeInt(i)
    return true
}
jesse-amano commented 5 years ago

@cstockton

I was about to protest that it'd be a little too easy to elide errors that way, but:

So, after weighing all the pros I can think of, and failing to notice any obvious downsides, I'm... on the fence. I suspect there are non-obvious downsides I haven't considered. But I'm starting to like where this is going, for however little that's worth to anyone.

lootch commented 5 years ago

I hope this is the right way to respond and I am not committing a serious faux-pas.

On 7/1/19, Chris Stockton notifications@github.com wrote:

@ianlancetaylor Has anyone suggested augmenting the ":=" operator to support "inferred returns" - Basically behave exactly as try without a function call. [ ... ]

What I find intriguing in this case is that we have something analogous to the "comma OK" paradigm, where now omitting the "err" assignee is permissible under some well-defined circumstances. Worth noting, but clearly not in itself sufficient to make this a valid proposition.

Lucio.

as commented 5 years ago

This bug is already possible, try or not - Go doesn't prevent you from writing bad code. Or turning this around, using bad code as a reason why try shouldn't be permitted is not a convincing argument. Instead, go vet should flag problematic cases.

@griesemer I don't agree. Although it can save keystrokes, pointer arithmetic was excluded from Go on the premise that it makes it easy to write bad, broken code. I feel that this is the same type of feature that will make bugs more difficult to detect.

data := try(ioutil.ReadAll(try(os.Open("foo.txt"))))

The typical example with try uses examples like the above, which to me obviously leaks a file descriptor. (The fact that Go hunts down and closes such descriptors in the runtime unbeknownst to users in the current implementation is something that might give us comfort, but there's a better example anyway).

data := try(ioutil.ReadAll(try(http.Get("http://example.com/")).Body))

The above reads like correct code, but ignores the requirement that the response body be read in full and then subsequently closed on the happy path. If you look at it long enough, the disgusting elegance of the incorrect example should make it apparent that we will be seeing these types of bugs in the future.

As someone who reviews more than writes code at this point, I would prefer that Go not add in features that make incorrectness so tempting.

cstockton commented 5 years ago

@jesse-amano Using the assignment operator actually prevents this case from being possible, without an explicit assignment statement the below behave exactly as they do today, i.e.:

json.Unmarshal(...)
(http.Handler)(h).ServeHTTP(w, r)

As for Values that just return an error are eligible for being returned like return json.Unmarshal(...) and can also be represented in the more compact form since there is no need for a value to exist outside the if block.

if err := json.Unmarshal(...); err != nIl {
    return err
} 

What I find intriguing in this case is that we have something analogous to the "comma OK" paradigm, where now omitting the "err" assignee is permissible under some well-defined circumstances. Worth noting, but clearly not in itself sufficient to make this a valid proposition.

The behavior would be identical to try without parens or arbitrary nesting & chaining. I think it will be hard to find something that a majority of people think feels natural without breaking the language. I admit since it seems the Go authors are pretty determined to add this type of feature to Go I am really digging deep for alternatives because I am absolutely convinced try in it's current form is not a good fit for Go. This is the closest thing I can think of that won't break BC, but maybe it still doesn'tt feel right to enough people. Either way I hope the try proposal is denied or someone comes up with something a lot more people can agree on.

edit: @jesse-amano I completely missed your point sorry! I suppose being inside a function that doesn't return an error would display a typical assignment count mismatch compile error? I imagine try would probably introduce a new type of compiler error message for that.

griesemer commented 5 years ago

@beoran Regarding https://github.com/golang/go/issues/32825#issuecomment-507126700 : Error handling is already different from situation to situation: Sometimes we return the error unchanged, sometimes we return a decorated error, sometimes we act upon an error, and sometimes we (can correctly) ignore an error. The only thing they all share (except when we ignore the error) is the err != nil test (which we already can do in more than one way). As much as it would be nice for a new language feature to capture all these cases (or 95% of them), such a construct is highly likely to interfere in non-orthogonal ways with other control constructs we already have. That is, the best we can hope for is making some of these cases (maybe 20%, maybe 50% of them) better.

griesemer commented 5 years ago

@cstockton Regarding https://github.com/golang/go/issues/32825#issuecomment-507136111: If nested try's are the only road block left and go vet is not good enough, I think we can consider disallowing nested try's - that would be easy enough. But at the moment I think we're still in the FUD (fear, uncertainty, and doubt) phase, with practically nobody having actually experimented with try seriously. I note that the people who have done so, have reported positively.

PS: The try issue is open again for feedback. Let's continue over there.

jesse-amano commented 5 years ago

@cstockton Oh, absolutely. To clarify, the point I was going for was that it's already bad practice to call functions like json.Unmarshal without capturing the error value in the majority of cases, but usually not considered bad practice to defer file.Close() instead of defer func() { err = file.Close(); if err != nil { ... }; }(), and we learned to tell the difference pretty easily. So it shall (probably) be with your proposed change. I initially flinched at the thought of somebody innocently using foo := strconv.ParseFloat(bar, 64) when they intended to handle the error, but after brief consideration I don't really think it's a problem after all.

griesemer commented 5 years ago

@sirkon Regarding https://github.com/golang/go/issues/32825#issuecomment-507167388 : Please leave such clearly unqualified statements out of the discussion - they have no place here. Just for the record, many ideas in Go are actually from the 80s (packages, separate compilation, etc.) rather than the 60s and many are much younger (goroutines, interfaces). These ideas may still appear old, but they have stood the test of time (at least the little time CS has been around).

griesemer commented 5 years ago

@turtleDev Regarding https://github.com/golang/go/issues/32825#issuecomment-507231353: Go does exception handling, and it's done with panic and defer and recover - we just don't call it "exception handling" because that term comes with a connotation that is misleading for Go. But just to be clear, Go can do all that raise with try-catch can do, and more (because in contrast to try-catch, defer can be used conditionally). Thanks.

griesemer commented 5 years ago

@sorenvonsarvort Regarding https://github.com/golang/go/issues/32825#issuecomment-507268293: If you want different error decoration for each case, use an if statement. Please see the design doc. This question has been answered many times by now. Thanks.

griesemer commented 5 years ago

@cstockton Regarding https://github.com/golang/go/issues/32825#issuecomment-507306652: Yes, we have thought about such a mechanism. Specifically, we have thought about "turning the tables" and instead of providing try, just provide handle, which declares an error handler. If a handler is present (and only then), one would simply leave away the err in the LHS of an assignment, and it would be checked implicitly (like with an invisible try). It does look nice, but it's also completely invisible, which is a big red flag. We do want exception handling to be explicitly visible in the code, at every place. Without that, it would be almost impossible to read code and see where error checking is happening.

turtleDev commented 5 years ago

@griesemer thanks for clarifying. but panic and recover have different use cases and for most part are very hard to find in any production code base. that leaves you with only a limited amount of control of flow constructs. adding a new construct would break that consistency as you now have a new control of flow construct that does something like return.

griesemer commented 5 years ago

@matthew-noken Regarding https://github.com/golang/go/issues/32825#issuecomment-507323973: You are proposing an interesting idea; it looks very much like a debugger's watch point mechanism. There are some questions that would have to be answered: Does the on block have to return (I suspect so, because otherwise you get into spaghetti code land)? Can one have more than one such on statement? How complicated can the on condition be (It will have to be evaluated upon each assignment)? Note that we cannot have arbitrary expressions because we have to identify the variable uniquely with the on statement. Also, somewhat an anathema in Go: The on construct implies invisible code to be executed elsewhere.

If you want to explore this more, I suggest to discuss this elsewhere (golang-nuts, or a different, new proposal). Thanks.

griesemer commented 5 years ago

@as Regarding https://github.com/golang/go/issues/32825#issuecomment-507345821:

pointer arithmetic was excluded from Go on the premise that it makes it easy to write bad, broken code

Actually, it was excluded because it would have made garbage collection difficult or impossible (and yes, one can also write unsafe code). But the more important point here is that there was concrete evidence and experience that supported this decision.

There is no experience and no evidence yet that nested try's are going to be prevalent or common. But see https://github.com/golang/go/issues/32825#issuecomment-507358397.

griesemer commented 5 years ago

@turtleDev Regarding https://github.com/golang/go/issues/32825#issuecomment-507368167 : A panic is exactly an exception, and recover inside a deferred function is essentially a catch. They may be harder to find in production code because in Go we do not recommend you write your code using exceptions; they should only be used in exceptional circumstances.

Regarding the number of control flow structures: The proposal is very clear that try is simply syntactic sugar, nothing else.

griesemer commented 5 years ago

I've tried to answer some of the recent comments in this thread on this thread. But let's continue new comments on the try proposal in the actual try issue #32437 (now unlocked again as of today); and leave this issue reserved for the leave err != nil alone discussion. Thanks.

ianlancetaylor commented 5 years ago

@cstockton Another comment about https://github.com/golang/go/issues/32825#issuecomment-507306652 : If we implemented this, then starting with

    func f() int { ... }
    ...
    x := f()

and changing to

    func f() (int, error) { ... }

would mean that the behavior of x := f() would suddenly and silently be very different.

freeformz commented 5 years ago

I ran some experiments similar to what @lpar did on all of our go repositories. The results are in this gist: https://gist.github.com/freeformz/55abbe5da61a28ab94dbb662bfc7f763

cstockton commented 5 years ago

@ianlancetaylor I actually feel this would work out very nicely in most cases, and make introducing better error reporting much less impactful. Consider full examples for the two main cases, first the caller returns an error:

func f() int { ... }
func a() error {
    x := f() // if f() is updated to return an error, we get automatic error propagation by default
    ...
}

func b() {
    x := f() // if f() is updated to return an error, we get the same compiler error 
    // assignment mismatch: 1 variable but pkg.f returns 2 values
}

I think in the common case this is a nice benefit to this approach, the corner cases where this creates a problem I believe are already brittle. Only one I can think of that could be truly nasty is deadlocking a mutex:

func (t *T) a() error {
   t.mu.Lock()
   x := f() // if f() is updated to return an error, we get automatic error propagation by default
   if x > 15 {
     t.mu.Unlock()
     return errors.New("t > 15")
   }
   ...
}

But I think code that is written like that is already susceptible to deadlocks if it relies on a foreign library call to succeed to have valid program state. If it is stored in a scope that can live beyond panic then it can deadlock if the same library introduces a runtime panic via NPE. In addition a main motivator for writing code like this is the historical cost of defers living on the heap. With the performance improvement of single-defers living on the stack such code isn't really necessary. I think any derivation of this type of error is easily remediated.

Finally like the arguments of supporting the shortcoming of "try" with tooling can also be applied here. Given the increased adoption of go-modules we have a nice opportunity to inject a "library-upgrade linting" step to put such changes in front of a users face clearly.

turtleDev commented 5 years ago

@griesemer

Regarding the number of control flow structures: The proposal is very clear that try is simply syntactic sugar, nothing else.

Pardon me, but try isn't going to be a macro (like C) so in effect, to the end user it's just another control of flow statement.

I believe I have no objective arguments at this point, so I'll concede that we maybe need better error handling, but I feel like try may not be the best solution.

Again, these are my opinions and I'm just representing them. I'm sure the Go team has put much more thought into this than I ever will.

agis commented 5 years ago

Side: It strikes me as strange that this issue has 1335 upvotes while the try proposal (#32437) has only 279 downvotes. I would expect people upvoting this to downvote the try proposal so that the community's feelings about it are more apparent, because these two proposals are mutually exclusive.

beoran commented 5 years ago

@griesemer

Error handling is already different from situation to situation: Sometimes we return the error unchanged, sometimes we return a decorated error, sometimes we act upon an error, and sometimes we (can correctly) ignore an error.

Agreed there, that much is obvious.

The only thing they all share (except when we ignore the error) is the err != nil test (which we already can do in more than one way). As much as it would be nice for a new language feature to capture all these cases (or 95% of them), such a construct is highly likely to interfere in non-orthogonal ways with other control constructs we already have. That is, the best we can hope for is making some of these cases (maybe 20%, maybe 50% of them) better.

The proposed try() statement also "interferes" with if and return in non-orthogonal ways, so I'd say that that argument is not correct. Some people here dislike try() for that very reason, but I disagree. Go is not Oberon, it is simple but not minimalistic, Go is more practical.

Where we disagree is that it is even worth while to bother with a language construct that, as you admitted yourself, has only limited use and applicability, and that can already be done correctly with if and return. I think many people, like myself, who gave their thumbs up to this thread are so underwhelmed by try() that they rather would not have it at all. Even if it not orthogonal with return, a more powerful, more generally useful try() is probably what most Go programmers would like to see.

ngrilly commented 5 years ago

@beoran,

You wrote that you would like a "more powerful", "more generally useful" try().

@griesemer mentioned 4 situations:

  1. Return the error unchanged
  2. Return a decorated error
  3. Act upon an error
  4. Ignore an error

try() solves 1 by design: it's literally a shortcut for if err != nil { return ..., err }.

Existing language constructs solve 3 and 4. We can already act upon an error with if err != nil { ... } and it will be difficult to find a more concise structure in that case. We can already ignore an error with _.

This leaves us with 2 (return a decorated error). The try() proposal suggests to use a defer statement to decorate the error, or if each error must be decorated differently then use a standard if err != nil { ... } construct.

The reasoning is well explained in this part of the proposal:

Our first iteration of this proposal was inspired by two ideas from Key Parts of Error Handling, which is to use a built-in rather than an operator, and an ordinary Go function to handle an error rather than a new error handler language construct. In contrast to that post, our error handler had the fixed function signature func(error) error to simplify matters. The error handler would be called by try in the presence of an error, just before try returned from the enclosing function. Here is an example:

handler := func(err error) error {
        return fmt.Errorf("foo failed: %v", err)  // wrap error
}

f := try(os.Open(filename), handler)              // handler will be called in error case

While this approach permitted the specification of efficient user-defined error handlers, it also opened a lot of questions which didn’t have obviously correct answers: What should happen if the handler is provided but is nil? Should try panic or treat it as an absent error handler? What if the handler is invoked with a non-nil error and then returns a nil result? Does this mean the error is “cancelled”? Or should the enclosing function return with a nil error? It was also not clear if permitting an optional error handler would lead programmers to ignore proper error handling altogether. It would also be easy to do proper error handling everywhere but miss a single occurrence of a try. And so forth.

The next iteration removed the ability to provide a user-defined error handler in favor of using defer for error wrapping. This seemed a better approach because it made error handlers much more visible in the code. This step eliminated all the questions around optional functions as error handlers but required that error results were named if access to them was needed (we decided that this was ok).

[...]

If we determine down the road that having some form of explicitly provided error handler function, or any other additional parameter for that matter, is a good idea, it is trivially possible to pass that additional argument to a try call.

Do you disagree with this reasoning?

beoran commented 5 years ago

I know this part of the proposal and I disagree with it, because the fact that questions were opened due to the idea of passing an error handler, doesn't mean we don't need such a feature. It just means we have to think well to answer those questions in a reasonable way.

Furthermore, now, we handle all 4 error use cases with an if err != nil statement, which is widely understood, consistent Go idiom. Only using try() for case 1, and possibly for case 2, if we don't mind the overhead of doing error wrapping in a defer statement, means the code for error handling will be split into if and try inconsistently, and if the error handling changes, we will have to switch between the one and the other.

A more powerful version of try(), that could be used in all cases would allow us to use try()almost always, and this would then become the new, consistent error handling idiom for Go.

However, with try() as it is now, it is not widely applicable enough, and there is just not enough convenience gained to introduce the aforementioned inconsistency in the error handling into our code bases. That's why I feel underwhelmed by the current try() proposal, and think doing nothing is better.

ngrilly commented 5 years ago

@beoran I think cases 1 and 2 have in common to return an error from the function (respectively without and with decoration), while cases 3 and 4 don't (respectively act upon an error and ignore an error). I think 'try()` focus is on cases 1 and 2.

What if the try() proposal could be improved to handle cases 1 and 2, by accepting an optional handler function? Of course, this requires to find reasonable answers to the questions listed in the proposal, but it seems tractable. Would you support something like that?

DarrienG commented 5 years ago

Here me out. If the case for this is we want users to check errors, we should probably add checked errors (kind of like checked exceptions). That way we're explicit as possible and the user knows they're checking all errors.

In all seriousness, if I were the one behind making these decisions, I would really like to repurpose the Kotlin ? operator or something like the rust unwrap() behavior.

Either of these I think would be an improvement:

getFile()?.getPath()?.toString()

where you get a nil back if there was an error along the way or

get_file().unwrap().get_path().unwrap().lossy_to_string()

where it blows up halfway through if there is an error. Rust deals with the second by having an expressive match function that lets you do an exhaustive search of the errors and handle each separately, all returning a value of some sort up the chain.

lootch commented 5 years ago

On 7/2/19, Nicolas Grilly notifications@github.com wrote:

@beoran I think cases 1 and 2 have in common to return an error from the function (respectively without and with decoration), while cases 3 and 4 don't (respectively act upon an error and ignore an error). I think 'try()` focus is on cases 1 and 2.

I'm somewhat disappointed not to see more discussion of my suggestion that it is the "return" part of "error return" that needs to be addressed, not the "error" part.

Then again, maybe I should have followed a more official approach. I'm simply no good at formulating proposals, I tend to go all over the place.

Cases 1 and 2 are, in my opinion, better served by a "fail" command keyword that clearly indicates (after some getting used to it) a change in the program flow and which is not subject to any inconvenient tradition as regards its full syntax.

What follows from that, however, whether we like it or not, is that the positioning of "err" as the last return parameter is soon to become law rather than convention. That is one of many "unintended" or "collateral" consequences that need to be taken into consideration.

There will be many other such consequences, from minimal to disastrous, some that only opportunistically piggy-back on the proposal. I would personally err on the side of caution, I do understand why the Go Team and Robert in particular are seeking support and no doubt are hurt that resistance turned out to be so great.

It's a clash of political ideologies, perhaps we need to dig a lot deeper to seek the real roots that need some gardening attention.

griesemer commented 5 years ago

@lootch Do you have a particular concern with error needing to be the last return parameter for try to work? Isn't that already a de-facto convention?

(As an aside, we're not "hurt" about the resistance - mostly flabbergasted about the outsize reaction.)

therealplato commented 5 years ago

@griesemer sorry for the hoary car analogy but the proposals feel like "We've decided we'll add either a diesel fuel system to all gasoline cars, or a battery and electric motor to all gasoline cars." Go's error handling is good enough. I'm concerned that the value added will be less than the costs, including new footguns and mental overhead.

timtadh commented 5 years ago

I just want to write a quick comment saying that having written a lot of Go (I have been using Go since its first public release in 2009 -- see my github) I would welcome improve ergonomics around error handling. While the explicitness of Go's error handling is nice it is also a pain in sequential code. The lack of introspection and typing around the actual values themselves (which is being addressed in a different proposal) does not in my experience actually improve the resiliency of the program.

I got annoyed enough at this few years ago I actually wrote some sugar around panic and recover to allow them to work (mostly) like unchecked exceptions: https://hackthology.com/exceptions-for-go-as-a-library.html . Actually using those wrappers is a bad idea because of were the community is at. However, I continue to believe improving the ergonomics around error handling is a win.

It seems crazy that people are ardently arguing for keeping an extremely verbose but unhelpful way of propagating error conditions. I have written and found real bugs in my code (and other's code) where people have messed up the if err != nil condition and created bugs. There is really no reason for those bugs to ever get written. Static checking can help but not eliminate these bugs.

I support the proposals to improve the ergonomics around error handling.

rsc commented 5 years ago

The proposal process is for, to quote https://golang.org/s/proposal, "Proposing Changes to Go." This issue is not proposing a change, so really it is a bit of a category error. If someone filed a proposal titled "leave select alone" we would just close it as not a proposal.

But really this issue is just an extension of the discussion of other issues like #32437, so I will leave it open as a good place to discuss "do nothing".

I've marked it Proposal-Hold to indicate that there's not a specific decision here, more of a meta-decision.

lpar commented 5 years ago

Side: It strikes me as strange that this issue has 1335 upvotes while the try proposal (#32437) has only 279 downvotes.

The try proposal was locked by the time I became aware of it, so I couldn't downvote it. I assume that's also why this proposal was entered to start with.

lootch commented 5 years ago

On 7/2/19, Robert Griesemer notifications@github.com wrote:

@lootch Do you have a particular concern with error needing to be the last return parameter for try to work? Isn't that already a de-facto convention?

I do in the sense that if that became "de facto" cast in stone, then that door should be swung open and make "error" an optional, special argument type and accept that, because for every right outcome there are more often than not many wrong ones and they may as well be addressed in a special way.

Once the idea that the "return" statement needs to be investigated more deeply (and the try proposal seems to be hinting in that direction), then the error condition is no longer "merely a value, and Go's approach starts to resemble the best house of cards built to date, but a house of cards, nevertheless.

I am a reluctant user of a few of Go's cleverer tricks (I have mentioned elsewhere that x++ and x-- do not feature - mostly, I slip up sometimes - in my code) so "try" will remain one of those to me, but I do not on principle object to its introduction, I merely feel that so much has been said, and still all possible downsides may not have been revealed (the unintended consequences of what I see becoming an eventual political decision). Putting that to the test may be good or bad.

What I see is that panic/recover got a bad rap and the injustice of it is coming back to haunt us. Again, I have yet to use either and I dread the day I have to because I'm not the sharpest cookie in the jar and I will find it very hard, but had panic/recover been encouraged for the rare occasions where it is suitable (none of them treated by Rob Pike in his wonderful presentation about errors being just return values, if I recall correctly), it would be much clearer to all that Go already handles errors as well as can be expected without the need to explore the swamp of possible options available beyond its boundaries.

That said, it makes as much sense to put try to the test, it is, after all, an optional feature. But the one side-effect is what this exchange is about: what will the consequences be of "de facto" compelling error handling functions to have an "error" type argument at the end of their return parameter list? How will that change the perception that "errors are just values"? How will it dovetail with the now much more analogous "comma-OK" paradigm? What else will this new principle give rise to?

Most of all, I think the mountain being made out of this molehill is indicative that Go has reached a life-changing milestone. Almost certainly, assumptions that were true in 2013, may well no longer hold. Not that that is news to anyone, but it suggests that Go2 may not be as wonderful to be backwards compatible with Go1 as has been too firmly proposed (in my opinion).

griesemer commented 5 years ago

@lootch Thanks for your detailed response. It is indeed very difficult to extend an existing language in a backward-compatible way, and so I'd agree with you that if that backward-compatibility were not required, perhaps one might do things differently. We will soon (once modules have become common place) be able to make language changes that don't require strict backward-compatibility.

As you say, try is an optional mechanism, and - I think it's worth repeating - despite all the hoopla around it, a very simple mechanism at that, easily explained using existing Go functionality - it's just that we can't write try as a function in Go ourselves (and generics wouldn't help either, for that matter). If we could, I'm certain that it would be quite common to see helper functions such as try for error handling in existing code. It is after all exactly what Rob Pike's talk about errors being values is all about: it's programming with errors.

It is still very surprising to me that there's such an uproar regarding adding a pretty minor helper function that everybody is free to ignore, yet people are seriously contemplating significant language changes such as on error which really do strongly suggest a specific style of error handling in the language.

Thanks again for your input. This is all very interesting.

Freeaqingme commented 5 years ago

I'm not sure if this proposal is the right place to discuss it, but given there's already a vivid discussion on the try keyword in this thread, I'll just consider it on-topic for now :)

I wonder if the Google folks would be willing to implement the try keyword in their internal Golang repo, and subsequently convert the existing Google code bases to use that keyword. By keeping it internal only, it would allow to revert it easily (that is, the burden is on a single company, rather than the entire community).

This would allow to do a little case study on this feature in a real-world high-sloc code base. Facebook has done something similar with PHP features in the past, and that way they were able to fine tune certain functionality before proposing it to the community at large.

If you were to write up a case study of how the try keyword was used in practice and what value it added in real life, that could provide a compelling case. If it (hypothetically) wouldn't provide any real world value, that would be valuable to know as well. Sometimes something looks really good on paper but doesn't work out in practice - or vice versa.

griesemer commented 5 years ago

@Freeaqingme We have already produced a tentative CL that shows how try might look like in the std library: https://go-review.googlesource.com/c/go/+/182717 (there may be false positives, but if there are, they are very rare). We are contemplating perhaps developing a tool that permits conversion of code bases in both directions.

You are also encouraged to use tryhard in your code base and report back.

Thanks.

Freeaqingme commented 5 years ago

@griesemer I may not have made myself clear. I assume Google uses their own build of Go internally. My suggestion would be to apply the patch you linked above to the internal Google build, and then convert (parts of) the Google code base - not just stdlib, but especially the internal projects that are written in Go. If Google engineers were to use it for a couple of months in real life situations that would provide good insights in how it would work in practice.

Obviously I can apply it myself as well and use it on my own code bases (and I might as well). But I'm just a single developer with a relatively small code base, so this wouldn't be as representative if a lot of Google employees were to use it.

Personally I'm still on the fence about this feature. On the one hand I do appreciate the fact that it may save me a couple of keystrokes each time. But at times I may be lazy (I'm human after all), and just nest as many try statements as I possibly can. Now I'm just going to be a bit more disciplined, were this feature present. But even if I was, there's still a chance that external libraries do overuse/abuse this keyword while my code base suffers from it (in terms of debugability, extendibility of those libraries).

griesemer commented 5 years ago

@Freeaqingme Got it. We could certainly run try over internal repos. I'm not sure we can convert and convert back - there's a significant cost involved here. Also, we could only report in aggregate (statistics) on this experiment as we wouldn't be able to report on internal details. That is the community would have no easy way to verify our claims. Finally, Google's code base may not be representative.

But thanks, point taken.

Freeaqingme commented 5 years ago

@griesemer I appreciate that it may be a costly endeavour. In which case I wouldn't do it. If it's simply a matter of applying your tryhard project than cost could be limited. But really that's something for a Googler (which I'm not) to determine.

I understand you wouldn't be able to report on individual Google projects or internal infrastructure. However, a few anecdotes could be shared, perhaps. But what I personally would find way more valuable is some Googlers reporting on how it worked out for them. Without going into specifics, statements like "I expected X but when I ran into cases like A and B I found that Y" I would find very valuable. I'd find zero need to verify those kind of reports.

Finally, Google's code base may not be representative.

It may be, it may not be. But there's a lot of people working at Google so I'd reckon most of the code base isn't based on how a single individual decided to write it, but rather be a culmination of contributions of many different contributors (employees). In that respect I expect things to be about as representative as things could get. There probably is no such thing like a 100% representative code base.

kataras commented 5 years ago

Keep it as it is, until we find a better solution, the try is not the one we were looking for. No need to take bias actions on this, take your time Go Authors. I strongly believe, as far as I speak with gophers around the globe every day, that the majority of the Go Community don't embrace the try proposal atm.

gonutz commented 5 years ago

Introducing a new syntax means that everybody has to upgrade their Go version. I am still using Go 1.10 because my workflow is based on the fact that I can go get things and then use them from the command line (my GOPATH is in my PATH). I recently had a problem when trying to go get someone else's library which uses modules. I got an error that .../v2 was not available. This means that there is already a split in code (think Python 2 and 3). For me there is a world before Go 1.11 and after Go 1.11. This is very annoying and introducing a new syntax for something that works as well as error handling in Go is not a good trade-off at all. It introduces more fragmentation.

lootch commented 5 years ago

On 7/4/19, gonutz notifications@github.com wrote:

Introducing a new syntax means that everybody has to upgrade their Go version. I am still using Go 1.10 because my workflow is based on the fact that I can go get things and then use them from the command line (my GOPATH is in my PATH). I recently had a problem when trying to go get someone else's library which uses modules. I got an error that .../v2 was not available. This means that there is already a split in code (think Python 2 and 3). For me there is a world before Go 1.11 and after Go 1.11. This is very annoying and introducing a new syntax for something that works as well as error handling in Go is not a good trade-off at all. It introduces more fragmentation.

If it's any consolation, I am in exactly the same position vis-a-vis Go modules. I haven't found the time and opportunity to get familiar with them, so I'm sticking with Go1.10 as well. Maybe that ought to be a survey worth having.

Lucio.

-- You are receiving this because you were mentioned. Reply to this email directly or view it on GitHub: https://github.com/golang/go/issues/32825#issuecomment-508372318

-- Lucio De Re 2 Piet Retief St Kestell (Eastern Free State) 9860 South Africa

Ph.: +27 58 653 1433 Cell: +27 83 251 5824 FAX: +27 58 653 1435

pongsatornw commented 5 years ago

I'm a new golang developer(still learning about go). I thinks that current error handling is good because it make us manage error easily. As a android developer, I thinks try-catch is harder to manage our error than if err != nil{ } in golang. And I think explicit error handling always better than implicit error handling.

PS. Sorry for my language.

Daniel1984 commented 5 years ago

leave it alone

reedobrien commented 5 years ago

It is not broken....

griesemer commented 5 years ago

Love the meme, @Daniel1984 :-)

Incidentally, the try proposal does leave if err != nil alone; it just gives you an additional option where it makes sense.

kroppt commented 5 years ago

I am of the opinion that try should not be included. On the inclusion of try:

Pro

Con

Daniel1984 commented 5 years ago

@griesemer that's exactly what I don't like. Things should be simple, I wouldn't want to add complexity to language just to have 2 ways of achieving same thing. There are patterns to avoid this if err != nil verbosity. https://www.ardanlabs.com/blog/2019/07/an-open-letter-to-the-go-team-about-try.html

marcopeereboom commented 5 years ago

The Go2 proposal #32437 adds new syntax to the language to make the if err != nil { return ... } boilerplate less cumbersome.

There are various alternative proposals: #32804 and #32811 as the original one is not universally loved.

To throw another alternative in the mix: Why not keep it as is?

I've come to like the explicit nature of the if err != nil construct and as such I don't understand why we need new syntax for this. Is it really that bad?

Very much this. Explicit code is correct code. The horrors I have seen with exception handlers are enough to walk away forever from that terrible unreadable construct.