12 ms·
Proposal: A built-in Go error check function, try
- lunixbochs 7y agoI implemented try() in Go five years ago with an AST preprocessor and used it in real projects, it was pretty nice: https://github.com/lunixbochs/og https://github.com/lunixbochs/og Here are some examples of me using it in error-check-heavy functions: https://github.com/lunixbochs/poxd/blob/master/tls.go#L13 https://github.com/lunixbochs/poxd/blob/master/tls.go#L13
- X6S1x6Okd1st 7y agoI wrote something similar but of lower quality about 5 years ago as well
- alexhutcheson 7y agoIt looks like it’s basically a less hacky version of what the RETURN_IF_ERROR and ASSIGN_OR_RETURN macros[1] do in C++. From experience, those work pretty well. That approach eliminates a lot of the boilerplate bookkeeping code, while still making it explicitly obvious to the reader that a given function call can fail. This seems nice, and I would definitely use it. [1] https://github.com/protocolbuffers/protobuf/blob/master/src/google/protobuf/stubs/status_macros.h https://github.com/protocolbuffers/protobuf/blob/master/src/...
- pcwalton 7y agoIt's the try! macro from Rust! Obviously I'm a big fan of this style of error handling, and I'm happy to see it proposed for Go.
- est31 7y agotry! is dead, long live try.
- danieldk 7y agoWell, try! is dead quite literally. Rust still has the macro, but it is superseded by the ? operator.
- est31 7y agoThat's the pun :). "The king is dead long live the king" originally comes from the French court and actually was used to refer to the old king dying and the heir to become the new king. https://en.wikipedia.org/wiki/The_king_is_dead,_long_live_the_king https://en.wikipedia.org/wiki/The_king_is_dead,_long_live_th...!
- amluto 7y agoExcept it’s missing the part where sum types are used, resulting in oddities about the non-error part of the return value when the error part is set.
- pcwalton 7y agoI mean, sure, but it's the best they can do while remaining compatible with all the Go code out there.
- hliyan 7y agoSo basically they're proposing: f, err := os.Open(filename) if err != nil { return …, err // zero values for other results, if any } can be simplified to f := try(os.Open(filename)) This makes a lot of sense, but I'm of two minds. On one hand, it makes things much cleaner. On the other hand, it might be a first step onto a slippery slope that ends with exceptions. A lot of others chiming in with different ideas on the original ticket: https://github.com/golang/go/issues/32437 https://github.com/golang/go/issues/32437
- jerf 7y agoAfter chewing on this for a while, I've come to the conclusion that the thing exceptions does wrong (or if that is too controversial, substitute "most dangerously") is that it disconnects handling the error from the scope that generated it. It's the way exceptions so easily fly up the stack into code that can't understand them because it is too distant in context that is the problem. Neither this proposal, nor any other I saw, would change that. I wouldn't even call this "a step in the direction of exceptions"; it's a neutral move. Everything is still occurring within the same scope, it's just getting spelled differently. Interestingly, this means that while pervasive use of "if err != nil { return err }" is technically very similar to exceptions when used everywhere, it can still have a different semantic meaning to a human reading the source.
- captncraig 7y agoEvery time I switch back to exception languages, I get this tendency to "assume everything succeeds all the time, and handle it at the very top level in case any part of it fails". I do not think about what can go wrong at each level nearly as much as I do when I am required to use `if err != nil` soup.
- nprateem 7y agoExactly. And in most cases I don't care. Can't open that file? That's a fatal error. Can't connect to the DB? That's a fatal error. Some file system operation failed? That's a fatal error. In the majority of applications it's normally pretty obvious which errors you're likely to care about (the user already exists, etc.) vs the rest that you just handle at the top level. The trouble with go's error handling is it makes you care about everything, which is just a waste of time and effort because the majority of errors will be fatal anyway.
- stepforward 7y agoIf there are multiples `try` inside a single function, and we are debugging and want to know which call raises error, how can we do that? Should `try` wraps the error and adds something more useful for debugging purpose? (the line number probably?)
- ithkuil 7y agoYeah, that was my first thought as well. I'm using the juju/errors library "return errors.Trace(err)", which annotates the error with the line number of the return. That wouldn't work in a deferred function. Perhaps the compiler could make that possible somehow although I suspect that might conflict with the stated goal of improving the efficiency of defer due to "try" encouraging more people to use it.
- akavel 7y agoA deferred function is called at the line where return would happen, so you can still access stack trace information required for errors.Trace(err) from inside a deferred function. You just need to go "one frame higher". (See the stack trace printed in: https://play.golang.org/p/Bpqdm8oWBF3 https://play.golang.org/p/Bpqdm8oWBF3). As a result, I believe juju/errors could then be extended with a new function, to be used like this: func foobar(...) (..., err error) { defer errors.Tracify(&err) ... } where: package juju/errors func Tracify(err *error) { if *err != nil { *err = TraceFromDefer(*err) } }
- grey-area 7y agoMaybe don't use this try if you want more detailed info on specific errors? It's for using where you would use if != nil return err not in every instance. If you try to use it everywhere it won't work well.
- dickeytk 7y agois it just me or does the word "try" seem to imply the opposite here? To me saying "try" is like saying "attempt to run this" like we have in a try/catch block. I feel like something like "expect" would more directly explain what this does. Great functionality though. I'd be very happy to see this included no matter what it's named. EDIT: This is answered in the FAQ section after I read further. Apparently it's because "try" is already a keyword. I still don't like it, but I get it.
- frou_dh 7y agoSee also the "continue" statement in C/Go/etc for something that does the opposite of what the word suggests.
- marcrosoft 7y agoPlease for the love of god no. Go is awesome for its simplicity. Errors should not be abstracted out of handling convenience. Errors are just values either eliminate the need for the error or handle it like you would any other value. Stop trying to make go work like every other language.
- axaxs 7y agoAgreed. This is the hill I'll die on. Boring error handling is what I specifically love about Go...
- mwaitjmp 7y agoOut of interest, how about the proposed check and handle changes proprosed for v2? Details here: https://dev.to/deanveloper/go-2-draft-error-handling-3loo https://dev.to/deanveloper/go-2-draft-error-handling-3loo
- axaxs 7y agoStill not a fan. These seem like schemes for people who are annoyed by errors, and just throw them over the wall... similar to say putting an entire python block in a try/except. With any sufficiently large application you start to realize how awful that is, especially when something fails and the only log is 'EOF'. I've learned to treat errors as first class citizens, because they are. I always add an annotation and stack entry via wrappers before returning them. Unless I'm missing something, that seems all but impossible with these schemes.
- deleted 7y ago[deleted]
- grey-area 7y agoThis isn’t a replacement for go error handling, it’s a complement. Sometimes you don’t need to do anything other than pass an error up the chain.
- codr7 7y agoI get that the error is still propagated "manually" behind the scene; but how is this different from exceptions in practice once you use try everywhere (except where you forgot and the error is dropped silently)? Here is my proposal: add restarts [0] as a complement to manual propagation. [0] https://github.com/codr7/g-fu/blob/master/v1/doc/typical_restarts.md https://github.com/codr7/g-fu/blob/master/v1/doc/typical_res...
- tedunangst 7y agoThe error can't go through a function that doesn't return an error, unlike exceptions. Also, you have to ask for this. Reversing the default is a big difference.
- deleted 7y ago[deleted]
- nmadden 7y agoUnlike unchecked exceptions.
- tedunangst 7y agoEven with checked exceptions, you have to be careful. You may not want exceptions from every function you call to fly up to the caller. function X() throws Oops { okToFail(); // fine if this failure goes up unexpectedFailure(); // not fine to go up } Unfortunately, once you tell the compiler its ok for exeptions to go up, that rule applies to the whole function.
- nmadden 7y agoI’m sure that’s a common problem.
- arcticbull 7y agoExceptions are the opposite of a goto statement, basically a 'comes-from' statement. They violate the principal of least surprise in every way possible -- you may have no idea what type is being sent your way, where from or what to meaningfully do about it as a result. They often result in memory leaks in languages like C++ due to lack of destructor invocation. try() errors can only propagate to the caller. As such, stack unwinding is clear, no memory issues arise, performance is good and locality of error is preserved. Surprise is minimized. While it may look similar, it's pretty markedly different in important ways.
- icholy 7y agoI think check/handle is much better than this.
- Insanity 7y agoI agree. I think they are focussing too much on backwards compatibility here. Java made that mistake in the last decade, hopefully Go does not repeat it.
- grose 7y agoSame. I don't like how this requires you to use named parameters and defer to add context to errors. It's way too easy to shadow err and I can see this causing a lot of pain in the future.
- umurgdk 7y agoI am strongly against this. `try` seems exactly like a function yet it is not acting like a function at all. People wouldn't expecting calling a function may return from the caller. And there is a reason why golang doesn't have macros. With macros all kind of craziness would be possible, and would really difficult to read different kind of projects' code.
- pjmlp 7y agoGo kind of has macros, like everything else, Go's solution to first level features in other languages is called //go:generate.
- lifty 7y agoI dislike this proposal for the same reason. Looks like a function but does not behave like a normal one. I personally don't mind the verbose error handling in go and I think it's clear and easy to follow.
- gnud 7y agoAgreed - I thought this looked pretty reasonable, if a bit parenthesis-heavy, until I saw this example: func printSum(a, b string) error { fmt.Println( "result:", try(strconv.Atoi(a)) + try(strconv.Atoi(b)), ) return nil } When you nest the calls to try inside another method call, like this, the control flow really becomes obscured.
- thisgoodlife 7y agoDo you think it would be better if it was called try_or_return?
- 0815test 7y ago> People wouldn't expecting calling a function may return from the caller. Why? This is essentially the same as the reset/shift pattern in e.g. Scheme - when you call shift from within a reset call, that might cause you to return directly from that. And that is a rather well-behaved pattern which composes quite well, it's nothing like the inherent weirdness of, e.g. call/cc.
- cpuguy83 7y agoError handling is, to me, the most important thing when writing any code. Promoting "if err != nil { return }" even more by giving it a keyword seems like a dangerous road to walk down. Are we proposing solutions to make developers not have to type "if err != nil" or are we looking at how we can help developers handle errors better and be more productive? I honestly don't have anything to offer as far as ideas for changes to the language, but I really hope we think through the long-term ramifications of these changes.
- jchw 7y agoI think the way Go error handling works is fantastic, and one of the few problems it has is repetition. There’s C++ macros for various environments that let you do the assign-or-propagate-error stuff with a bit less repetition, but I dislike them because they feel opaque; people don’t often feel the potential consequences and just use it as a way to not think about error handling. With Go, the error handling behavior is so obvious and in your face that it’s painful. (This doesn’t stop bad error handling hygiene, but it has definitely helped me.) Sadly, there’s no obvious dumb way to reduce that repetition. I would not mind a language mechanism similar to defer, like the handle/check proposal for example. The most obvious issue I take with try is that it looks like a function call but is much more magical :( and also, I dislike named returns.
- marcus_holmes 7y agoI've spent the last year working on a Go system, writing Go every day. After a couple of months, I just stopped seeing/worrying about "if err != nil {". It has become punctuation, the Go equivalent of semicolons; I don't even use snippets; I manually type that every time. Which is good, because it does make me think about whether this function call can error, and what I should do about it if it does. 90% of the time it's ("just pass it up"), 5% of the time it's ("nothing, I don't actually care if this routine fails") but 5% of the time it's ("right, yes, this needs to be dealt with here"). When reading code, I just skip over it if it doesn't do anything interesting. I understand that in English reading, we don't notice "they said"; our brains just skip over it, and you can use "said" to open every quote and it won't feel repetitive. That's how I've got with "if err != nil {" so... I'm against this "try" stuff, because it feels like it's been proposed by people who haven't worked with the language too much. I'm not against making it more friendly for new people to learn, but this doesn't feel like that. This just feels like "ugh, really? I have to handle all the errors?!"...which is not a reason to change it.
- pjmlp 7y agoWell, yet another magic function.
- frou_dh 7y agoIt seems so ad hoc. It's not really design when one just papers over a very specific shortcoming.
- boomlinde 7y agoI agree that overloading the semantics of function calls is ugly, but to play the devil's advocate, does it have to be "design" in that sense if it truly does address the shortcoming in a practically sufficient manner? Aside from not appealing at all to my sense of esthetics, I have no qualms about this because it does address a problem that a lot of people have complained about in a way that I could get the idea within a couple of minutes of reading the proposal. That said, a keyword might have been nicer.
- frou_dh 7y agoI suppose the difference between a "quick fix" in a language and in a codebase, is that the former will be crystalized forever. Language designers seem to like to bang the drum about the ethos of "Orthogonal Features". I know Go's have, in presentations. But that seems to be cast off when the going gets tough. See also "Contracts" in Go generics, which is mostly the same thing as the existing Interfaces feature, but working around the fact that interfaces didn't have a good story for symbolic operators. Orthogonal? Smorthogonal!
- tapirl 7y agoA little similar to this one, https://gist.github.com/yaxinlx/1e013fec0e3c2469f97074dbf5d2e2c0 https://gist.github.com/yaxinlx/1e013fec0e3c2469f97074dbf5d2..., but with more considerations for details.
- abvdasker 7y agoThis is interesting, but I think it doesn't necessarily fit the language well. 1. Go has very few keywords relative to other newish languages like Swift and Kotlin. Introducing a new one should only be done if the benefits are undeniable. 2. It causes an early return without any use of the "return" keyword, which feels pretty weird. 3. It's a bit weird that err will be magically defined in a defer function if the surrounding function includes a "try". Does err have to be declared earlier in the function? If yes, it's strange that it seemingly never gets assigned. If there are multiple variables of type error in the function which one gets assigned the result of "try"? 4. Probably most importantly it doesn't feel very explicit. In some languages this may not be a problem, but Go is designed to be very explicit and this seems a bit incongruous with the rest the language's style. Maybe I'm just not understanding the proposal. I do like how concise this is. It's nicely backwards compatible and allows existing error handling to stay the same.
- ngrilly 7y ago1. As explained in the proposal, try is not a keyword, but a built-in function. 2. Returning if there is an error is the whole point of this proposal. 3. I don't understand your point. Could you provide an example? 4. try is explicit. There is no implicit behavior or stack unwinding.
- uryga 7y agoit uses the same syntax as functions, but it doesn't behave like a function – functions generally can't manipulate control flow like that. so calling it a function is weird because it's closer to a language construct like `await`
- leni536 7y agoInterestingly enough C++ has a recent proposal that converges to a similar design from the other direction (i.e. exceptions). http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2018/p0709r2.pdf http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2018/p070... It's still a lot more implicit than go's design, as it's meant to be compatible with regular exceptions.
- airencracken 7y agoNot a fan. I don't mind the boilerplate to be honest.
- jopsen 7y agoI suspect the downside is that it'll promote blind propagation of errors. In rust that's fine because the type system will document what error types can be returned. In golang, it important that every error type that can be returned is manually documented. Otherwise, it's better to just panic, since nobody can handle unknown errors anyways.. Or am I missing something?
- cientifico 7y agoThe interesting part for me about the current form is that it makes me think what should be the state on the rest of the ecosystem in case an error happens. That extra lines, as for experience, pays off quite fast. This form puts to the background that though, and I fear I feel tempted to put try everywhere.
- grey-area 7y agoDon't do that then. This is just another tool in the toolbox, you don't have to use it at all. I think I'd use it in about half the cases where I return an error, in cases where I simply want to handle it one level up without further annotation.
- politician 7y agoGo is a managed language, and that management extends to how you are allowed to write the code. There aren't simply tools in the toolbox, fmt and lint enforce programming styles. In this community, it's accepted that if you didn't run `go fmt` on your code before committing that someone else will do it for you. If you don't fix all of the linter errors, you can expect contributions to be rejected. If try is adopted, we can expect to see the linter pushing its usage aggressively. The OP will be pressured to use it, and ultimately has no say in whether they use it less than the linter demands and social expectations for idiomatic usage compels.
- grey-area 7y agoI see no reason for the linter to recommend it unless you are using if err != nil {return err} - if you are, it is functionally the same, therefore no change and it would be recommended, which is fine IMO. I don't really see the danger here - if you want to annotate errors properly, do so, if you want to respond in place (with a retry for example), do so, if you don't do either and just return the error (which is sometimes fine) yes the linter would recommend the shorter version. Where's the problem?
- Insanity 7y agoI love Go, but error handling can become cumbersome. Though, I also think a lot of that feeling is because at the codebase I'm working on we return too many errors in a strange way. We're returning pointers almost everywhere along with an error, even simple methods that could do fine without. But the n you feel obligated to check them instead of ignore them, just in case someone does change the implementation of the simple function. I feel like in some of the projects I wrote for myself, I use a lot less error handling and then I don't mind the 'if err != nil' approach anymore. I'm sure this `try` keyword will help deal with the pain from the codebase I'm working on, though it doesn't adress the root of our problem :( To give an idea, in a function that does 5 other function calls, I need to check error returns 5 times. Every. Single. Line. That can't be the norm, right? :P
- kkarakk 7y agoYour second line is the answer - Go is opinionated, Go expects you to do things in a certain way(a way that follows standard specs in most cases). If you start bydoing a workaround in your architecture, you will similarly have to start doing workarounds in your Go code and it becomes SUPER messy super quickly.Google can afford to follow standard specs slavishly because giant company with tons of money. Maybe you can't... It's one of the pitfalls of using an opinionated language. I don't think introducing something like this will work because it is also a hack that will propagate other hacks in your code. Just return errors in a more standard way(or stop using Golang)
- jy3 7y agoLooks a lot like the already proposed check/handle keywords that was met with a lot of push back from the Go community: https://go.googlesource.com/proposal/+/master/design/go2draft-error-handling.md https://go.googlesource.com/proposal/+/master/design/go2draf... Doesn't look like the proposal adds anything new.
- pjmlp 7y agoIt is from a core team member, usually those proposals tend to win in the end, as already occurred a couple of times.
- ngrilly 7y agoThis new proposal is explicitly a simplification of the previous check/handle proposal. And for the record, there was a lot push back, but also a lot of support for the check/handle proposal. Language design is an iterative process.
- bsaul 7y agoI used to be a bit skeptical on the slow pace at which go decided to add new feature, and the insane care they took to have orthogonal features, but after seing the recent swift language evolution ( with google team pulling it toward dynamic features, and now apple adding weird and clumsy DSL support) i must say i’m now completely supporting their choice. taking a year before settling on a less ambitious but more orthogonal and minimalist feature is a sign of wisdom. it is amazing the speed at which a language can go from something elegant to a mess.
- kstenerud 7y agoIf it were a keyword rather than a pseudo-function, I would support its addition. Psuedo-functions should not be allowed into the language, as they tend to have side effects that you wouldn't expect from a function call (such as returning from caller to grandparent). Some keyword that implies "return if error" so that you could then do: try v1, v2, v3 := someFunction() You could even make it a little bit smart, taking advantage of the return types of the function to automatically fill in named return values: func MyFunc(param int) (result int, err error) { result = 0 if param > 0 { result = param + 10 } else { try p1, p2 := GetInternalValues(param) result = p1 * p2 } return result, err } where "try p1, p2 := GetInternalValues(param)" is syntactic sugar for (in this case): var p1, p2 int p1, p2, err = GetInternalValues(param) if err != nil { return result, err }
- deleted 7y ago[deleted]
- 013a 7y agoGiven that putting 'try' before the variable definitions would probably interfere with the 'var' keyword, it makes a bit more sense to me that it should be put after the assignment operator (something like var try v1 = Thing() is probably lexable consistently, but it would be confusing to remember if it goes var try v1 or try var v1, unless it supported both which seems ish) v1 := try someFunction() I like that, primarily because it has echos of how the go keyword already operates. But the important thing is that it should be disallowed anywhere that isn't directly next to an assignment, which might be difficult. Like // compiler error fmt.Printf(try someFunction()) Not sure how easily that can be enforced without affecting the lexability of the grammar or compilation speed, but in a way, it should be thought of as a tokenized part of the assignment, not the RH-expression, such that the assignment tokens become = := = try := try
- panpanna 7y agoWhy not simply using a "?" the way kotlin and rust do?
- ajcodez 7y agoIt’s contrary to Go core values of simplicity and avoiding slow patterns (using defer statements for error handling). It seems like a net loss and I would expect the proposal to be rejected.
- gregwebs 7y agoI hope the custom handler part is reconsidered. It is fine to 4 panic if the handler is nil or it returns nil.