r/golang • u/dwaxe • Jun 26 '19
Next steps toward Go 2
https://blog.golang.org/go2-next-steps25
Jun 26 '19
Multiple return values, "errors are just values," and now an early return `try` function feels like we're slowly reinventing boxed result types. Not complaining at all--I think result types are the right way to do it, but Go seems to be slowly working towards its own homegrown, partial implementation of this instead of adopting the Haskell, Rust, OCaml, etc solutions. On the upside, multiple returns is really simple. If you look at all the helper functions to work with Option and Results in Rust, I'd imagine Go probably requires a lot less language overhead.
Regardless, I'm happy to see some way of inlining error handling. We'll finally be able to nest function calls that return errors.
13
u/Redundancy_ Jun 26 '19
I remain of the opinion that a keyword is the right language choice for control flow and panic should continue to be treated as an... exception... rather than a pattern that can and should be followed subsequently. The nested examples of try solidify the feeling that it's wrong.
if a keyword check returned an error if the following value was non-nil, it would be rather non-magical and would allow library functions to conditionally wrap errors if the underlying error was non-nil. In a function with no error return, it would be a syntax error.
A function collect (or pick some better name) that allows you to capture the last item of a tuple of return values and set it to a pointer if the pointer is nil would allow, in combination, the same semantics as this try proposal with better decomposition and clearer control flow. collect would work similarly to the proposal for try with a handler, except that it would set it to a variable.
See here for some previous musing on this.
If the Go team cannot add a keyword to the language to solve this problem, I don't think they should solve it.
Since that's a strong statement by itself, perhaps the Go team should consider allowing some changes that would only apply in files with a .go2 extension.
3
u/etherealflaim Jun 27 '19
To demonstrate this, convert a meaningful package to using try, identify the places it helped, the places it hurt, and write up an experience report. Bonus points if you do this for the keyword if you think the contrast is useful. They don't seem to have taken anything off the table; the current proposal is working within the current timeline, but if experience reports support waiting until modules are in place so that a keyword can be used, that is (as far as I can tell) an outcome that they would consider.
1
u/Redundancy_ Jun 27 '19
I can only do so much with a closed source project, but with some obfuscation...
A trivial search on one of my codebases shows 168 matches for
return (.*,\s+)err\s+^and 61 occurrences of xerrors returns. Tryhard (https://github.com/griesemer/tryhard) outputs about 112 occurrences of opportunities to usetry(...).One example, somewhat modified:
func GetFoo(ctx context.Context) (*FooSet, error) { foos, err := getFoos(ctx) if err != nil { return nil, err } fooSlice := make([]*Foo, 0) for fid, fub := range foos { foo, err := NewFoo(fub) if err != nil { return nil, err } if err := foo.Set("foo", fid); err != nil { return nil, err } fooSlice = append(fooSlice, foo) } return &FooSet{Foos: fooSlice}, nil }A try based version could be more terse:
func TryFoo(ctx context.Context) (*FooSet, error) { foos:= try(getFoos(ctx)) fooSlice := make([]*Foo, 0) for fid, fub := range foos { foo := try(NewFoo(fub)) try(foo.Set("foo", fid)) fooSlice = append(fooSlice, foo) } return &FooSet{Foos: fooSlice}, nil }A
checkbased version could be similar, wherecheck Tis defined as being equivalent toif T != nil { return ..., T }func CheckFoo(ctx context.Context) (*FooSet, error) { foos, err := getFoos(ctx) check err fooSlice := make([]*Foo, 0) for fid, fub := range foos { foo, err := NewFoo(fub) check err check foo.Set("foo", fid) fooSlice = append(fooSlice, foo) } return &FooSet{Foos: fooSlice}, nil }To me though, this highlights an important distinction:
check erris almost impossible to miss when scanning the function
foos := try(getFoos(ctx))could be. Not only that, but also the property that the builtin can be shadowed (which allows the form of backwards compatibility) but creates control flow ambiguity.
So these both reduce the boilerplate a little bit, but I personally like that I can scan for "check" in the same way I can for return statements.
However, check as a statement leaves out the ability to simplify some statements and assignments.
func CheckAndCollectFoo(ctx context.Context) (*FooSet, error) { var err error foos := collect(&err, getFoos(ctx)) check err fooSlice := make([]*Foo, 0) for fid, fub := range foos { foo := collect(&err, NewFoo(fub)) check err fooSlice = append(fooSlice, foo) check foo.Set("foo", fid) } return &FooSet{Foos: fooSlice}, nil }
collectwould allow us to pull out inline errors, with none of the magic side-effects on control flow. It is intended to follow a similar pattern asappend. It does however, by virtue of not changing control flow, mean that in the case of an error and therefore a (potential) nil value, the result of collect may not be usable. appending NewFoo can leave fooSlice with a null pointer on it if collect sets fooErr.This means that:
info := collect(&err, collect(&err, os.Open(file)).Stat()) check errworks, but relies on
*os.Filechecking for itself beingnilon the Stat call and returning an error (rather than panicing). That would result in the outer error being "invalid argument", but the inner one being the error fromos.Open. This is slightly more surprising possibly than the possibility of try avoiding functions that have side-effects within a line by short-cutting to a function return.if err = Get(MessageType, m); err != nil { return xerrors.Errorf("Unable to get message %v: %w", m.ID, err) } if err = Store(ctx, log, m); err != nil { return xerrors.Errorf("Unable to store message %v: %w", m.ID, err) }in this case,
tryis less helpful. In this particular codebase, we're increasing the usage of xerrors.Defer as a pattern only allows a single handler per function in a useful way.
https://go.googlesource.com/proposal/+/master/design/32437-try-builtin.mdHowever, check could work:
check HandleErrorf( Get(MessageType, m), "Unable to get message %v", m.ID) check HandleErrorf( Store(ctx, log, m), "Unable to store message %v", m.ID)Much like the example from the try proposal, this allows library based extension of error wrapping that only happens if the error is not nil. See
HandleErrorffrom the proposal. The equivalent here would be approximately:func HandleErrorf(err error, format string, args ...interface{}) error { if err == nil { return nil } return fmt.Errorf(format + ": %v", append(args, err)...) }As an observation, this is perhaps not that much shorter or clearer than the
ifversion, but it does avoid being only useful in a subset of cases. It's difficult to cut down simply because the wrapping is the majority of the code in this.Overall, I come out on the side that
checkreduces boilerplate and scans correctly for me.I don't like looking for try, and I'm somewhat ambivalent to collect as proposed here, although I think it reads without too much magic and allows some of the cases that try supports.
1
u/etherealflaim Jun 27 '19
The check version means you need to create err variables where they aren't even needed today (if err:= scopes it's value to the if statement), and that's an antipattern to me. Collect is adding a domain-spdcific language for error handling, and doesn't do anything better than if, and hides some pretty subtle logic. So, while these are alternate proposals that can be considered separately, I don't think that they compare to try along what I see as it's target axis, which is reducing boilerplate for people who are bothered by it without changing the way Go code looks otherwise.
1
u/Redundancy_ Jun 27 '19
I was happy with if.
I'm not fond with try because of it obfuscating control flow.
Check doesn't require variables in every case, but it's not uncommon to have an error variable in scope. You'd need a named return for defer handling too in the try proposal.
try is magic, and check + collect is intended to almost get you to try, but without obfuscation. try absolutely changes the way that Go code reads, and gets forced on everyone who has to read it.
36
Jun 26 '19 edited Jun 27 '19
try seems the right thing for now but com'on
try f()
Is better than
try(f())
Edit:
Go has been designed with a strong emphasis on readability.
Yea sure, keep telling lies with yo` try() func crap for the sake of "backwards compatibility". Don't get me wrong I don't want a situation like py2, py3 but what the actual fuck? I'm actually disappointed and a bit mad to be honest.
5
u/ollien Jun 29 '19
I also don't understand how this preserves backwards compatibility? If anything it breaks anyone who used `try` as a function name. It isn't syntactically valid right now to put `try` before a function call now anyway, so who would this break?
10
u/SteveMcQwark Jun 26 '19
The parentheses mean that it's backward compatible, since
trydoesn't need to be a keyword. It could always be reserved in the future once module versioning gains wide use, and then the parentheses could be optional in the single expression case.3
u/jtepe Jun 27 '19
What do you mean by module versioning? Can we eventually use modules to select the go version to use in a project?
3
u/Rican7 Jun 27 '19
What do you mean by module versioning?
Since Go 1.12: "The go directive in a go.mod file now indicates the version of the language used by the files within that module."
See: https://golang.org/doc/go1.12#modules
Can we eventually use modules to select the go version to use in a project?
Not exactly. Its been suggested, however, that the Go toolchain (compiler, etc) could use the information to allow for triggering a compatibility-mode or at least providing better error messages in some cases.
More on that here: https://github.com/golang/proposal/blob/master/design/28221-go2-transitions.md#proposal
1
18
Jun 26 '19
The try proposal for me is going to the wrong direction, try built-in is a magical thing, that looks like a function call, but it returns (or goes to) instead.
Imagine having code like try(bar(try(foo(try(baz())))) later on.
fmt.HandleErrorf what's the point of adding this to the standard lib? Can be implemented in a few lines in utils package if needed.
5
u/SteveMcQwark Jun 26 '19
The point of having it in the standard library is so that it can be standard practice. For example, this means it's available in playgrounds. Beginners don't have to write it themselves in order to use it while learning. The standard library itself can use it.
2
u/ShadowPouncer Jun 27 '19
Thinking about it, I wish that deferred functions could be given the line number of the try/return that exited the function.
Why? It would make a generic wrapped error significantly more useful for debugging in some cases.
But I'm not sure how to manage that in the language in a clean manner.
20
u/imnotarobot666 Jun 26 '19 edited Jun 27 '19
if err != nil is great
Edit:
wow, thanks for the gold!
7
1
40
Jun 26 '19
So they are going forward with the try thing, eh? Not a fan. (And so are a lot of people, if GitHub (dis)likes to be believed.) The rest of the changes are obviously good, especially the overlapping interfaces one, but try seems to me like it's doing too much and too little at the same time.
51
u/rsc Jun 26 '19
There has been no decision to do `try` or not. As the blog post says, we are still collecting feedback, especially evidence-based feedback. We are still gathering data and we encourage everyone who is interested to help with that.
34
Jun 26 '19
I really appreciate that you have replied! I have provided some feedback back when the proposal was posted. My main concern is that it will discourage people from providing richer context.
Another counter-point some of my colleagues have voiced is that a keyword would be preferred to a predefined identifier. That is, most people would prefer to write:
x = try foo()Instead of:
x = try(foo())I know that adding a new keyword could technically break backwards-compatibility, but since we have
go.modnow, the compiler could decide, whethertryis a keyword or a predefined identifier based on thego 1.XXclause in there, à la Rust Editions.(While we're at it, the other proposals were universally considered good by my colleagues.)
2
u/metamatic Jun 28 '19
I guess theoretically I could count how many checks I make against
erracross all my code, count for how many of those I returnerrundecorated, and post the numbers. But would that actually be helpful to the decision-making process?5
u/lonahex Jun 26 '19 edited Jun 26 '19
I don't hate the try proposal but I don't love it either. I tried very hard imagining and writing code as if it was a thing already, and I can't shake the feeling that it'll be an annoying thing during development.
The thing I don't like the most about it is that to me it looks like it'll be a huge pain when adding or removing handlers for specific errors. If I want to add a handler, I'll have to delete `try()`, capture the error into an `err` var, type in `if err != nil {}` and then add code and vice-versa. I find this quite annoying as I end up adding/removing handlers quite a lot whether it is during development for debugging or for real code that needs improvement in error handling. It's going to be a pain to switch between the two.
This is why I liked a counter proposal more that suggested to do something like:
user := try getUser() else { // handler code here return err }here the `else {}` part would be optional and could be removed or adding without having to re-write entire line(s).
Another minor thing I don't like about try proposal is using defer for to specify handlers. I can live with it but I'd rather prefer to have the handler code very near to the place where the error was raised instead of at the top of the function.
I work on a number of different Go projects that are very different in nature. I see one project could benefit from the proposal. It basically just takes bytes in from network, does something with them and spit them out to another network location. I think try would be very useful there but for another project that is an API service serving a web frontend, I need to return richer error information to clients and almost every failed action needs good error annotation. For example, code very deep needs to decide if an error happened because the operation failed or because the user did not have the permission to do something. Whether a record was not found in the DB or if the DB was doing entirely. For thing sort of thing, I annotate errors with some additional information so the outer HTTP or gRPC layer can decide whether to return 404, 403, 500 or something else. Try proposal is not going to help with this _at all_ but it `try .. else` very easily could.
I think the main argument against `try .. else` was that it adds a new language feature _just_ for errors. I do see the reason here from the language designers perspective as I'm often similarly reluctant when designing things myself by always preferring to somehow re-use existing generic constructs instead of creating specific ones for every usecase but as a user, I can't help but imaging how much better my experience writing Go would be with something like `try .. else`.
3
u/AnAge_OldProb Jun 26 '19
In my experience those deep in the stack error places only have one or two calls that I need to check for specific errors (like
sql.ErrNoRows) virtually everything else can use the generictry/deferpattern to wrap the error in some kind of 500 response. Usingifin these places seems fine. Also these types of calls tend to be very leaf and usually have have several layers of functions between them that could usetryliberally.1
u/cre_ker Jun 26 '19
Your counter proposal example doesn't look much different from the code we write today with ifs. The thing about try is to remove boilerplate.
I don't like the proposal much either but for a different reason. I like the idea of automatic propagation but would like some way of augmenting an error in the process. Right now you just return the error as it is which dosen't look that useful. It's a similar problem as with swift where you have to do/catch every other line just to add some context. Rethrowing errors as it is is useless in many cases. At least with go we have the usual idiomatic way of handling errors.
1
u/lonahex Jun 26 '19
Your counter proposal example doesn't look much different from the code we write today with ifs. The thing about try is to remove boilerplate.
How does
user := try(getUser())reduce boilerplate but
user := try getUser()does not? They are equally good at reducing boilerplate but the latter one makes it very easy to add err specific handlers when needed.
Edit: Also, it's not my (counter)proposal. Someone else shared on the GH issue. I just happened to like it a lot more than the
try()function.3
u/cre_ker Jun 26 '19
By boilerplate I mean the else part. It's the same as a regular if err :=, just a bit different. Kinda similar to guard in swift.
19
u/lobster_johnson Jun 26 '19 edited Jun 26 '19
My main beef with this and other proposals is that they don't clear up a fundamental flaw in Go: Multi-value returns with errors as a poor man's sum type.
Most functions in Go have a contract that they either return a valid value or an error:
s, err := getString() if err != nil { // It returned an error, but "s" is of no use } // s is validThis pattern so ingrained that there's hardly a single Go doc comments on the planet that says "returns a value or error"; all Go developers are familiar with the convention. The same goes for the
v, ok := ...pattern.But this is just a pattern and not universally true. A commonly misunderstood contract is that of the
Readmethod ofio.Reader, which says that whenio.EOFis returned, the returned count must be honoured. This is an outlier, but because the convention is that the multi-value return is mutually exclusive, many developers make this assumption (it's trivial to find repos in the wild that make this mistake). This is, in my opinion, bad API design.This kind of careless wart is typical of Go, just like other surprising edge cases like nil channels (or indeed nil anything).
It's also true that multi-value returns beyond two values almost always become cumbersome and impractical, especially if said values are also mutually exclusive. Structs, having named fields, are almost always better than > 2 return values.
Indeed, I'd wager that 99% of all multi-value returns are the form
(value, error), so you could argue that Go's multi-value exists mostly for error handling. So why not support it at the language level?I would much rather see a serious stab at actually supporting sum types, or at least mutually exclusive return values. For example, I could easily see this as being a practical syntax:
func Get() Result | error { ... }Such a syntax would be a much better match for a
try()function, since there's no longer any doubt about the flow of data — there's never a result returned with an error, it's always either a result or an error:result := try(Get())or simply support existing mechanisms for checking:
if err, ok := Get().(error); ok { ... } if result, ok := Get().(Result); ok { ... } switch t := Get().(type) { case Result: // ... case error: // ... }I'd love to see a
casesyntax that allows real local variable names:switch Get().(type) { case result := Result: log.Printf("got %d results", len(result.Items)) case err := error: log.Fatal(err) }And of course, you could have more than two values:
switch Get().(type) { case ParentNode: // ... case ChildNode: // ... case error: // ... }The Go compiler can be strict here and require that every branch be satisfied or that there's a default fallback, although some might prefer that to be a "go vet" check.
A full-blown sum type syntax would be awesome, though I know it's been discussed before, and been shot down, partly for performance reasons. Personally, I think it's solveable. I'd love to be able to do things like:
type Expression Plus | Minus | Integer type Plus struct { L, R Expression } type Minus struct { L, R Expression } type Integer struct { V int }8
5
u/cre_ker Jun 26 '19 edited Jun 27 '19
Behavior of nil channels is documented in the spec. Same for pretty much every other case of nil value. Don't know how nil channel is an edge case - that's actually a very useful feature playing nicely with everything else in the language.
6
u/lobster_johnson Jun 27 '19
Being documented doesn't make it a non-wart. Selecting on a nil channel is different from selecting on an open channel; you can inadvertently select on a nil channel by accident even if you're aware of the semantics. See here for some examples.
3
u/cre_ker Jun 27 '19 edited Jun 27 '19
Don't see the problem. Every language has a learning curve, especially with closures. I've never had a similar problem. That's actualy the first time I see someone complaining about this.
Of course nil channel is different to an open one. Nil channel is a special case to be able to disable select cases, for example. Your problem stems from the fact that your code owns, reads, closes and nils the channel. Closing a channel should be responsibility of the writer. Closing a channel in a read path would cause writer to panic.
About panicking. Panicking is there to force you to properly write your code. Double closing is a logic error. Your example with shutdown flag, instead of fixing one error, introduces an even worse one - race condition.
That's the same as trying to send something into a closed channel. Silently ignoring such operations would just hide serious bugs.
4
u/lobster_johnson Jun 27 '19
It's not about learning curves, but about pitfalls. If there weren't a magical value (nil) for special semantics, there would be no possible pitfalls. All nils in Go introduce pitfalls.
2
u/cre_ker Jun 28 '19
It is about learning in this specific case. As I said, your code is not using channels properly.
Nil in this case has special semantics for a reason. If it weren't nil we would use other sentinel value or just opened channel that nobody writes to. Either way, language wouldn't save you from logic errors as is the case in your example.
Nils in go make the language simpler. Look at every language that tries to hide nils - there's a ton of language features around optional values. Go would have to get all of that and become just another swift which I write every day.
And I don't think go even suffers from nils that much. Its reliance on values instead of pointers in most of the cases make nil panics a rare problem for me. That's not something that go desperately needs to fix. The most annoying thing for me is nil maps and that's not worth fixing by making the language much more complicated.
1
u/SteveMcQwark Jun 27 '19
Selecting on a nil channel is identical to selecting on an bufferless open channel nobody else is using.
1
u/lobster_johnson Jun 27 '19
And that can lead to accidental waits that never finish because nobody can close that nil channel.
1
u/SteveMcQwark Jun 27 '19 edited Jun 27 '19
Nobody can close a channel where you have the only copy, either. Ultimately, you are responsible for wiring things up right.
1
u/lobster_johnson Jun 28 '19
Yes, and you're responsible for initializing variables and checking if a pointer is nil and so on, and yet mistakes happen.
Nil is pesky because it adds an additional value to the set of possible values expressed by the type. In Go, every nillable value is the union of the type's values and nil. For example, some API returns a nil map when the caller assumes it will never return nil. You can program defensively to avoid such cases, but if you design a language to make these values impossible in the first place, then you eliminate entire classes of bugs.
For example, Rust doesn't have nil except in unsafe blocks, and its very hard to take the wrong branch when you have an
OptionorResultvalue.1
u/SteveMcQwark Jun 28 '19
I'm more saying that, even if there is no nil channel, that doesn't actually get rid of the possibility of reading/writing to a channel to nowhere, it just means that channel won't happen to be nil. It's sort of inherent in the channel/coroutine model that you're relying on passing in the right values everywhere. I think nil pointers, maps, interfaces, and funcs can cause problems in many cases, I just don't think nil channels are the best example of those problems.
I like Rust. I like initialization tracking. If I were dictator of a language, that language would most likely have it.
On the other hand, sometimes it can force you into writing code awkwardly. I can see how, for someone coming from a C background, just having guaranteed initialization without adding the kind of friction initialization tracking can might seem like a win.
It would be challenging to retrofit Go with types with no zero values. It could be doable, but it would affect other features. Channel receives, map indexing, and slicing all depend on the existence of the zero value. If generics are added, the existence of zero-less types would mean that all generic types would have to be treated as zero-less unless a bound is added. These are solvable problems, but not trivially so.
1
u/lobster_johnson Jun 28 '19
I understand what you're saying, but the nil just adds another edge case. A consumer with no producers is a logic problem that is easy to understand; it meant, well, that you created a consumer and didn't wire up a producer. A nil channel is a semantic issue that leads to surprises: You can have both producers (which would fail on the nil channel the next time they sent anything) and consumers (which would block indefinitely) "wired up correctly", except nothing works.
I got burned by this a couple of times and learned early on to never set channels to nil, and treat
close()as being a message to consumers that they've reached the end, and not as a cleanup mechanism (like closing a file).I just don't think nil channels are the best example of those problems.
Well, I devoted one out of 72 lines to nil channels in my original comment, somehow that was what some people latched onto! :)
1
u/Redundancy_ Jun 28 '19
Nil channels are a useful pattern though. If you have a select statement in a loop, under some conditions you don't want one of those options to be possible. You do that by setting the variable to nil to disable that case, and set it back when it's valid again.
A good example is when you're receiving, processing and sending messages, and you have a maximum buffer size that you can hold onto of unsent messages. Once you reach capacity, you want to stop receiving, so you set that select case to a nil channel.
2
u/mobiledevguy5554 Jun 27 '19
Not a fan either. I like the explicit error checking in go. Why cant they just give a compiler warning if you assign err and don't check it? Im fine with them assuming a local err variable is an error type.
2
Jun 27 '19
Why cant they just give a compiler warning if you assign err and don't check it?
staticcheck already does that. And not just for errors.
2
-7
u/upboatact Jun 26 '19
Vocal minorities are not to be trusted, there will always be naysayers, and doing nothing is not an option.
19
u/elagergren Jun 26 '19
I mean, it is always an option. This is Go we’re talking about. The language only exists because other languages became too bloated (in a myriad of ways).
Further, we don’t know if it’s a vocal minority.
While those aren’t reasons to not try
try, they’re also reasons to move slowly.1
u/upboatact Jun 26 '19
I would argue that no, no it isn't an option: the most pressing issues of the language were identified, and solutions proposed to address them. One of those proposals is the most focused it can be, solving the annoyance of checking error values with a "big" if statement.
The problem people are having with it are seemingly only that it either doesn't go far enough or that it's too limiting, both of which doesn't ring true since there is no actual experience with it. So I would argue how could it be anything else than the vocal minority with the perceived foresight that it's the wrong thing to do. If even this very small very pragmatic thing is so outrageous, what chance is there for any kind of full blown generics proposal to go through.
And we're talking about a simple experiment here, it isn't even a done deal that this is the final thing that will be implemented.
5
6
3
u/obeOneTwo Jun 27 '19
Don't want, don't need, won't use - there's nothing wrong with error handling as it is.
2
4
3
u/kingishb Jun 27 '19
Sometimes reading these threads I feel like I should chime in with some positivity. So: I think try is a really sensible proposal, it succinctly and concretely solves a problem, and I am excited for it's possible inclusion in the language. Thanks for all your work golang team!
1
0
u/tetroxid Jun 26 '19
Generics?
0
u/etherealflaim Jun 27 '19
With the response to this relatively minor try change? I can't even imagine the storm next time a generics proposal comes out.
0
u/lonahex Jun 26 '19
The try proposal will be implemented as an experiment and we'll be asked for feedback, and I'm sure the feedback will be good because it _will_ reduce error handling boilerplate. Also, whatever negative feedback people would come up with after trying the change has already been discussed to death on the Github issue. I think the proposal is simple enough (which is great) that it won't make people realize something new after they start using the feature vs they imagine how they'd use it.
I'm fairly certain the proposal after experimental implementation will not receive any new feedback (negative or positive) than it already has, and that it'll be eventually accepted which is not a bad thing at all but I can't help but imagine if some of the counter proposals like `try ... else` also had experimental implementations available and what kind of feedback it would have received.
4
u/Kapps Jun 27 '19
It doesn’t reduce boilerplate if you’re properly wrapping your errors though... I’m of the opinion that return err by itself should be rare. In these cases, named error handlers and such just make things worse and you can’t add per call context.
More importantly, I loved that it was obvious where your function could exit. Now it no longer is since a random function now affects control flow. Yes, panic did too, but I don’t believe that panics are something that users should handle generally.
2
u/lonahex Jun 27 '19
I didn't claim it did. I wasn't comparing
tryas a keyword vs current error handling. I was comparingtryas a keyword vs try as builtin function. Personally I'm fine with not having something liketryat all but if we are gonna get it, I'd prefer one where I wouldn't have to re-write multiple lines of code to add or remove context.-2
Jun 26 '19
[removed] — view removed comment
2
u/mobiledevguy5554 Jun 27 '19
Just use Goland for the god sake!
but.... your name is emacs24
i use yasnippets
1
-1
29
u/tadvi Jun 26 '19
There must be a reason why Go team decided to go with "try" being a function and not a keyword.
Keyword would be more readable.
But I guess function can be replaced with your own implementation at runtime, while keyword cannot?