golang / go

The Go programming language
https://go.dev
BSD 3-Clause "New" or "Revised" License
122.28k stars 17.47k 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?

icholy commented 5 years ago

@sirkon what are you basing that statement on?

sirkon commented 5 years ago

@sorenvonsarvort it doesn't seem that bad to me:

Code like the above code can be a lot cleaner than if you had to sprinkle in if err != nil blocks. Go is all about "linear readability" so I think try does well towards that end.

In Russia we call this «экономия на спичках». Use google translate to get a meaning.

velovix commented 5 years ago

For those in this thread that haven't already, I would recommend reading this comment on the original try proposal issue. It discusses general error context best practice and how it might be expressed with try.

I think that perhaps error context has become a bit dogmatized in the Go community. I know I've personally fallen for this and over-contextualized my errors, resulting in very long, repetitive, and hard to read messages. There's a lot of nuance regarding when to contextualize errors and when not to.

tusharsoni commented 5 years ago

I like that try is basically a shortcut and reduces some boilerplate code. But we lose the ability to wrap the errors with extra information. However, the following change might fix that:

f := try(os.Open(filename))

becomes

f := try(os.Open(filename), "open data file")

Of course, if you need to do a lot more than that, the "full" way of doing an err != nil check is still available.

andrerfcsantos commented 5 years ago

I agree with this, but i'm going to respect the request from the go team to have more experience with the change before having a final opinion.

But my preliminary experience with the change seems to support that it is really unnecessary. I have 2 "real world" programs with about 10k lines each and running tryhard on both shows that none of them would benefit from this change. This is easily explained by the fact that both always add context to errors. I have other smaller "toy" programs in Go and tryhard did find 1 case where i could've used try in one of them, but that's it.

I admit that other people may treat errors differently than me and i admit the possibility that try can be used in a positive way. The tryhard source code itself has some consecutive cases of return err, that if it were to use try i don't think legibility would be compromised that much. But i just fear the misuses, because those will impact legibility. A good example is provided here. And then determining what is a good use or not will be a whole other story.

Also, i like how people usually can just read go code even if they don't program go themselves. This will make them have to learn the magic try does, specially because it does a different thing than the other try they've seen in other languages. This is true also for new people coming to the language, it's just another feature they'll have to learn in a language that takes pride in being simple by having "just the features you need".

Let's wait and see. I'll be experimenting more with this change, but i'm not positive it'll change my position.

There was overwhelming community feedback requesting more streamlined error handling (from the annual survey). The Go Team is now addressing that issue.

@icholy Like i said, i do like to add context to errors. In that regard, "More streamlined error handling" for me means better ways to provide that context and to get information from it. For instance, with all the context i added to my errors should be trivial to ask "the context" if the error was caused by a timeout. But it's not. You usually have to resort to use pkg/error, make your own "error structures" and/or make methods to work with them that depending on implementation can resort to do string searches. I'd rather see something that would save me from making whole structs and methods than things that save me a single if very occasionally at best. And like it was previously said, can you really call it "error handling" when this change is basically providing a more convenient way to not really handle the error?

twitchyliquid64 commented 5 years ago

you think try is complicated?

In isolation try is not complicated, but language changes are not considered in isolation. Consider:

I echo above sentiments that simplicity (having a single way to check errors) is more important than a brief way to check errors.

kgrvamsi commented 5 years ago

Is it not go's way of handling a err is through that global statement and if this is something to be ignored then How are panic situations handled ?? I would support for a better error handling as other programming paradigms handle errors today but not to ignore

NamPNQ commented 5 years ago

I don't see any problem with try proposal? If u want to use old behavior or need other way handle error, why not re-use old way? No-one sticking a knife to your neck presses you use try syntax? try syntax is simple, and a beatiful way to reduce boilerplate code, I don't know why gopher love sadism-way?

adiabatic commented 5 years ago

What if gofmt were changed such that one-statement if blocks stayed on one line?

That way, we could have wrapped errors take only one line to handle instead of three for most cases. This would reduce the vertical space taken up by error handling and make it seem like error handling isn't taking up over half of the vertical space in the average function.

Here's what it would look like:

// we already have an err in scope
err = frub.Confozzle(foo, bar, baz)
if err != nil { return errors.Wrap(err, "confozzling didn't work") }
thomasf commented 5 years ago

I think that perhaps error context has become a bit dogmatized in the Go community. I know I've personally fallen for this and over-contextualized my errors, resulting in very long, repetitive, and hard to read messages. There's a lot of nuance regarding when to contextualize errors and when not to.

The error wrapping and stack frame/error printing stuff would make a lot more straight forward. I think that there will be a good solution for that in time but not right now.. Personally I would prefer to wait with introducing more powerful go2 features until parametric polymorphism is settled because it can potentially be useful when designing the rest of the features

henvic commented 5 years ago

I totally agree with leaving it as is. It's a little bit too verbose but it's pretty straight-forward to follow.

If I could just reduce

if err := foo.x(a, b); err != nil {
    return err
}

if err := foo.y(); err != nil {
    return err
}

if err := foo.z(c); err != nil {
    return err
}

to something like

if err := check foo.x(a, b), foo.y(), foo.z(c); err != nil {
    return err
}

maybe would also be great without changing the idiom too much IMHO.

sanbornm commented 5 years ago

@henvic I think the problem with that is that you are assuming you want to handle the three errors in the same way. Go forces you to think about how to handle each error individually. Having separate error checks makes this clear. Aggregating them into one makes the dev have to cognitively go back and check, should these errors really be treated in the same way? I think with this proposal we loose clarity and forced thinking around errors for a few less key strokes.

henvic commented 5 years ago

@sanbornm, you are right. I agree.

This check could too be a checkNonZero operator that would only take one return argument and return at the first non-zero value (like null). But besides being too vague, and affecting the language even more. It would even only slightly give a hint that you shouldn't use it if expecting, say, io.EOF. Another possibility would be, perhaps, to count on go vet to at least let you know of the most common cases (like io.EOF when reading from a pipe)... but this idea doesn't sound good at all to me.

s-kostyaev commented 5 years ago

I don't see any problem with try proposal? If u want to use old behavior or need other way handle error, why not re-use old way? No-one sticking a knife to your neck presses you use try syntax? try syntax is simple, and a beatiful way to reduce boilerplate code, I don't know why gopher love sadism-way?

We all live in community. Every day I work with a lot of code written by other people. So change in language will affect me even if I don't use it.

jesse-amano commented 5 years ago

In large projects, the worst logs are generated by a function we forgot somebody wrote, which calls a library which calls a library which calls a library which threw a generic new Exception() that nothing caught until a "Pokemon" exception handler did and logged a generic error. The second-worst are the same, but with an inscrutable several-hundred-line stack trace, with which I guess we can eventually figure out the cause (luckily just searching for github.com/<us>/<ourproject> finds most of the relevant information, but there's sometimes a lot). Despite their name, "Exceptions" are distressingly unexceptional in big Java projects.

Meanwhile, even when there's a lot of redundant context, simple Go error strings like "narf: Error unpoiting the zort: foo: Unexpected bar in baz: {\"ork\": \"morpork\"}" have been (in my experience) typically very easy to interpret, as long as we've been diligent about embedding the important context somewhere in the actual error value. If important context turns out to be missing, that is also usually fairly obvious. The "fix" in those cases is adding more context and waiting for another error, so it's not perfect, but on balance I still prefer this to scrolling through stack traces and/or relying on the dependencies of the dependencies of my dependencies to "throw" or "raise" sane error messages. I really appreciate how the name panic() seems to prevent most Go developers from so liberally deploying what is essentially the same language feature. Let's not make error and panic the same thing after all.

I've occasionally run into situations where a function had a dozen or so failure modes, and the vast majority of those deserved the same error message. The repetition doesn't really bother me, but there's usually someone else on my team it does bother, so we compromise by declaring a closure at the start of the function to handle those common errors.

func foo(a, b, c SomeArgType) (x, y, z SomeReturnType, err error) {
  handleError := func(handleErr error) (x, y, z SomeReturnType, err error) {
    log.WithFields(logrus.Fields{
      "package": "foo",
      "func": "foo",
      "arguments": map[string]SomeArgType{"a": a, "b": b, "c": c},
      "error": handleErr,
    }).Error("Error fooing the bar")
    return reasonable, default, values, handleErr
  }

  err := doABunchOfThings()
  if err != nil {
    return handleError(err)
  }
}

Which, admittedly, is still an imperfect solution in some ways. But I like that doing this made it very easy for future developers to still understand when and what foo returns, without control flow jumping around too much.

If somehow this repetition "problem" is extremely common throughout numerous packages instead of (as I usually see it) confined to a handful of irreducibly-complex functions in an irreducibly-complex package, a project-wide "functor" could probably be used to a similar end, and (sigh) if a concept of parametric types ends up getting added to the language, you could hide even more detail behind an error-handling-factory-factory without needing to rely on try/catch.

velovix commented 5 years ago

@thomasf

The error wrapping and stack frame/error printing stuff would make a lot more straight forward

I agree.

Personally I would prefer to wait with introducing more powerful go2 features until parametric polymorphism is settled because it can potentially be useful when designing the rest of the features

A few people in the original try proposal discussed waiting for generics as well, but it's not clear to me how parametric polymorphism would make the try proposal any different. It's already a built-in, so it's not confined to the limitations of what we can express in the language.

Freeaqingme commented 5 years ago

@icholy

There was overwhelming community feedback requesting more streamlined error handling (from the annual survey). The Go Team is now addressing that issue.

Just to respond to this; there also was a majority in the UK in favor of Brexit. Sure, the EU also brings about some disadvantages that the general public responded to. However, once all the alternatives were brought up, it seemed like staying in the EU wouldn't be so bad after all.

Now it's not at all my intention to politicize this, and you may disagree with the above. But what I do mean to show is that even when a majority initially considers something to be a nuisance, it still can be the best solution once all the alternatives have been examined.

I'm not strongly opinionated about the error handling, but it could be an argument for leaving things as they are.

requaos commented 5 years ago

In a professional coding environment, we take advantage of current error handling practices to annotate tracing systems and decorate logs. As an aside, the implicit return is rather like using panic in an exported library function, in that it obscures the immediate flow-control readability.

sirkon commented 5 years ago

@icholy

There was overwhelming community feedback requesting more streamlined error handling (from the annual survey). The Go Team is now addressing that issue.

Just to respond to this; there also was a majority in the UK in favor of Brexit. Sure, the EU also brings about some disadvantages that the general public responded to. However, once all the alternatives were brought up, it seemed like staying in the EU wouldn't be so bad after all.

You don’t need to consider this person’s claim seriously: Emojis’ count shows people are generally don’t like try proposal and they generally like this “leave it as is” one.

PS in my practice a huge majority of people who dislike Go in its main domain (network services, CLI utilities) haven’t even used it. So I would prefer to ignore their opinions.

donpark commented 5 years ago

We need better, less controversial, options than the try proposal. I don't see the urgency for hasty solutions.

jesse-amano commented 5 years ago

@velovix I think I hate parametric polymorphism more than try/catch error "handling", but if it did become a language feature, I could see a few ways it'd sidestep the need for another builtin language feature.

For one thing, when the code people don't want to repeat is boilerplate like:

foo, err := Foo()
if err != nil {
  log(err)
}
bar, err := Bar(foo)
if err != nil {
  log(err)
}
// ...

Then some combination of parametric functions, type inference, and maybe or optional style object design patterns would straightforwardly (heh) reduce the boilerplate without resorting to weird non-linear control flow strategies:

func<T> DoWithErrorLogging(f func(any...) (T, error), args... any) T {
  t, err := f(args...)
  if err != nil {
    log(err)
  }
  return t
}
// ...
foo := DoWithErrorLogging(Foo)
bar := DoWithErrorLogging(Bar, foo)

IMO this all would be much, much worse than Go1. But better than having this plus try/catch keywords in the language.

Honestly... As things stand right now, I think my favorite "breaking" changes for Go2 would just fix all the little inconveniences in Go1, like the net/http defaults being mutable shared globals nested inside mutable shared globals (just make Hashicorp's cleanhttp standard, basically), or (*time.Timer).Reset() having a useless return value that you just have to know about, or the entire syscall package. Go3 can be released almost immediately after that with whatever tumors people want to grow on it; I don't see why small and big breaking changes need to all be done in a single release.

mpictor commented 5 years ago

I'm in favor of try... when used sparingly. I suspect that popular projects will add guidelines on when/if they're ok with use of try, and that small/new/single-person projects will sometime suffer from poor errors - and thus lack of use - due to trying too frequently. Those projects will die off or be fixed or forked.

I really don't see addition of try to the language to be all that horrible. If people's worst-case fears prove founded, its use will be frowned upon. Inexperienced people will use it indiscriminately, while others won't. It's not the end of the world.

If try is added, I'll probably use it in some cases. Those cases are, where an error is returned but I think it's so incredibly unlikely for there to actually be an error that I don't see the point in adding context, and I simply return the error as-is. For example, if I've just created a filesystem that fills a disk I know is 1 TB, I can be certain there's space to create a 1kb file or a directory. If that fails, I don't want to ignore the error - it could indicate a bug elsewhere, hardware failure, etc. However, it's not really worth putting effort into annotating every insanely-unlikely error.

honzajde commented 5 years ago

I don't like how try(..) is just putting one more pair of parentheses/brackets on a coder's mind stack to think of when typing. And for the longest time I can imagine!

So this is better: value, err := foo() return err if err != nil

But still this is so common. So I would like if smt like this could be somehow possible:

value, check err := foo()

as commented 5 years ago

If Go wants to be a readable language, the ability to do thinks that are difficult to read or understand should be minimized.

If Go wants to have good error handling, it should encourage errors to have additional context as they ascend up the call stack. The requirement of using defer to handle errors seems confusing. What if your error handler has an error? Defers are executed in stack order, do we need to declare handlers in reverse?

If statements are straightforward, and leave little room for ambiguity here. I feel like try is solving a nit rather than a real engineering problem. I like this proposal because it allows the silent majority to finally make a statement without fully understanding every facet of these complex proposals.

ianlancetaylor commented 5 years ago

@icholy Please be polite. Please be aware of the Gopher Code of Conduct: https://golang.org/conduct. Thanks.

ianlancetaylor commented 5 years ago

Everyone: in addition to my comment above (https://github.com/golang/go/issues/32825#issuecomment-506740412) please be aware of https://golang.org/wiki/NoPlusOne. It is not helpful to make a comment saying little more than "I agree" or "I disagree." Use the emoji buttons instead. Thanks.

ianlancetaylor commented 5 years ago

@sanbornm

(I agree that it is possible to reply a message; I said it was awkward, not impossible. And my point about threading still stands, in that this mini-thread is lost in a blizzard of other comments.)

A proposal to me means a call for change. This particular issue is anti-change. Do you propose that we create a proposal to not change error handling? I think the proposal system is great but it leaves the status quo underrepresented.

It's not necessary to create proposal A saying that proposal B should not be adopted. Vote down proposal B instead. For detail discussion of proposal B, use that proposal or the mailing list.

(I understand that the proposal B in this case is locked; the fact that this proposal has 77 comments in less then a day shows why. This level of discussion simply works better on a mailing list rather than the issue tracker.)

sanbornm commented 5 years ago

@ianlancetaylor

(I agree that it is possible to reply a message; I said it was awkward, not impossible. And my point about threading still stands, in that this mini-thread is lost in a blizzard of other comments.)

Fair enough, that makes sense. Mailing lists are great but I personally find it easier to contribute through GitHub in this case. I don't have much to say other than the current error handling is great and I wish that it remains the same. Emoji/votes are great for this. You probably don't want 100 people writing "Please leave error handling alone" in the mailing list where 100 "votes" would suffice.

Because this issue is locked it can no longer be "voted" on with Emojis. Which is why I believe this issue was created in the first place.

A side point but related, dependency management wasn't handled well. Dep worked great and go mod was chosen (form what seemed) out of nowhere [1]. I understand it this is why the proposal system was created. I just feel like the proposal system in this case might under-represent the community if issues are locked and we are told to go to mailing lists.

[1] https://twitter.com/_rsc/status/1022588240501661696

Edit: The Go team and community for the most part do an amazing job listening to the community feedback. I appreciate all the work that goes into it. The Go surveys are a great example.

sirkon commented 5 years ago

@sanbornm

Dep worked great

Need to disagree here. Go modules finally solved that ill known “gobindata” problem with their persistent caching by https://proxy.golang.org

That dep dude didn’t even realize the problem and was toying with fancy “ensuring” via VCSes instead .

sanbornm commented 5 years ago

@sirkon This is a little off topic but you don't need any of that if you vendor deps like Dep was doing.

cstockton commented 5 years ago

As it stands I think I would rather leave things as they are unless more constraints were added, like 1 try statement per line. Reason is consider this example from the proposal - it seems harmless enough info := try(try(os.Open(file)).Stat()) but it leaks file handles beyond the scope of what normal control flow would. I think we will see an increase in file resource leaks with io.Closer implementations or other cleanup function that people may evade in the pursuit of more compact code.

Maybe some people will consider it's inconsequential because f will no longer be live and thus eligible for GC immediately and at some point the finalizer will ensure that f is closed. I think that it changes the previous clear (linter supported) conventions of using defer today which are bound to a function block. When the function block is exited the resource is freed. Relying on the garbage collector provides no guarantees that you won't exhaust resources (typical open file handle limit defaults can be between 1k and 4k) - which is easily exceeded with a simple filepath.Walk that doesn't close the files it stats.

In summary I think this syntax as implemented offers a subtle risk in resource management in Go since it lacks the ctor/dtor and relies on lower level GC machinery far removed from code blocks to prevent resource leaks. Turning something that seems harmless into a potential error condition (too many open files).

var n int
for _, name in try(os.Readdir(...)) {
   n += try(getSize(name))
}
func getSize(name string) (int, error) {
   return try(try(os.Open(name)).Stat()).Size
}

edit: For the constraints, I actually think if it was only valid on the right hand side of an assignment that would be better than saying 1 per line, since a, b := try(get("a")), try(get("b")) is reasonable enough. But it still leaves the ability to do try(os.Open(name)).Stat() - which if you were to make try() void, but only when not on the RHS of an assignment you're left with something that isn't very function like at all.

sirkon commented 5 years ago

@cstockton wow great finding!

Rust actually has similar macro (? if I remember correctly) which does exactly what this try meant to do, but they have proper raii there, so it’s not a problem in that language and a huge hole in our case

@sanbornm yea, keeping half of the internet in your repo looks like a great idea indeed.

ghost commented 5 years ago

Finishing Touch

lpar commented 5 years ago

Since someone mentioned a utility that would count the number of places where try would save me time/effort, I decided to run it over my largest work projects, plus everything else in my old GOPATH GitHub directory.

Project LOC* try candidates
cal1 2047 3
pump1 1030 0
docs1 4576 8
hugoutil 604 1
everything else 8452 23

Bear in mind that the contents of "everything else" includes quick hacks and code I wrote when I was learning Go.

My overall conclusion is that for me at least, the try proposal wouldn't help streamline my error handling to any worthwhile degree.

therealplato commented 5 years ago

The biggest reason I love go is, its spec restricts coders to a small subset of syntax available to other languages. Because it's such a small feature set, it's easy to learn the entire feature set. A future developer can probably look at my code and know what I did. Each new thing added to the language decreases the chance that future developer knows that thing. The extreme of the slippery slope is a language whose complexity makes it hard to grok, like C++ or scala. I'd like to see no syntax additions to go 1. Put them in go 2 instead.

sirkon commented 5 years ago

@miekg please add this link https://github.com/golang/go/issues/32825#issuecomment-506882164 into the proposal. The example completely disqualifies the whole idea of this recent try keyword.

vladaionescu commented 5 years ago

image

jochasinga commented 5 years ago

I totally agree with leaving it as is. It's a little bit too verbose but it's pretty straight-forward to follow.

If I could just reduce

if err := foo.x(a, b); err != nil {
  return err
}

if err := foo.y(); err != nil {
  return err
}

if err := foo.z(c); err != nil {
  return err
}

to something like

if err := check foo.x(a, b), foo.y(), foo.z(c); err != nil {
  return err
}

maybe would also be great without changing the idiom too much IMHO.

If you were talking about a "Maybe" type, it requires variant type first.

nathj07 commented 5 years ago

Let's keep if err != nil it works, it's clear, it's really not that verbose, and it makes sense in the flow of the code. As you read code with this construct you know what it is going to do. Let's keep it, let's not add try

feloy commented 5 years ago

When I read code, I want the lines doing the job to be clearly readable, without or with a minimum of error handling stuff.

The 3 letters 'err' at the same level are acceptable to me. I would not like some 'check' function wrapping the important code, because the important code would be at a second level (remember lisp?), and I would not like a 'try' a line before, because the important code would come indented and on the second line.

res, err := begin_job() if err != nil { handle_error() }

err = continue_job(res) if err != nil { handle_error() }

With this code, you can read the flow of the non-error case by reading the first lines of blocks (as I read the titles of docs when I have to read it quickly)

thomasf commented 5 years ago

Since someone mentioned a utility that would count the number of places where try would save me time/effort, I decided to run it over my largest work projects, plus everything else in my old GOPATH GitHub directory.

Project LOC* try candidates cal1 2047 3 pump1 1030 0 docs1 4576 8 hugoutil 604 1 everything else 8452 23

  • Go code only, excluding comments, according to cloc utility.

I think that try is more needed in larger programs. Just drawing from memory I think that programs of with LOC sizes around 15-20k and upwards need them more because that is when you might start to get layers which just need to pass on errors because they are aptly specified and handled in a closed system by both the sending and receiving side. It depends a lot on which sort of program it is though. I probably would not use try much in smaller programs either

davecheney commented 5 years ago

I think that try is more needed in larger programs.

Good point. I tried tryhard on heptio/contour, 28.7k lines of source text, tryhard found 12 substitutions.

sirkon commented 5 years ago

I think that try is more needed in larger programs.

Good point. I tried tryhard on heptio/contour, 28.7k lines of source text, tryhard found 12 substitutions.

WOW! 12 vs 28.7K lines, this really needs dedicated keyword!

Well, I am more interested in your POV on this:

stat := try(try(os.Open(fileName)).Stat())
thomasf commented 5 years ago

I think it's more common if your program is a bit more monolithic and not part of a service integration between many services. When i just search for fmt.errorf or errors in github on that repository (heptio/contour) there is only very few results so it's hard to get a quick overview.. But as I said it probably varies a lot from program to program, even for larger programs.

Say that you have a single program which does not use a lot of external libraries. Then you can have a specific AuthorizationError (and you know all errors returned are specific enough with any io errors already handled and wrapped) which already contain your user meta data and can be propagated without change a few layers without much change to things that actually need to handle them until the request layer.

sirkon commented 5 years ago

I think it's more common if your program is a bit more monolithic and not part of a service integration between many services. When i just search for fmt.errorf or errors in github on that repository there is only very few results so it's hard to get a quick overview.. But as I said it probably varies a lot from program to program, even for larger programs.

Say that you have a single program which does not use a lot of external libraries. Then you can have a specific AuthorizationError which already contain your user meta data and can be propagated without change a few layers without much change to things that actually need to handle them until the request layer.

You don’t get an idea. Annotations are to easily find a way error occurred. We similarly have os.NotExist but this is barely a good hint on error path.

davecheney commented 5 years ago

@thomasf here is another data point, from a several year old working copy of juju/juju,

529628 source lines, tryhard found 1763 (0.3%) replacements.

thomasf commented 5 years ago

Yeah, sure. Since you have been involved with both they are probably not great examples of different ways to write programs though. I don't have time to even try the tryhard program atm and even less doing a proper run of it on varied sources (which might be impossible to collect anyway because it omits closed source code if being collected via github)

bitfield commented 5 years ago

529628 source lines, tryhard found 1763 (0.3%) replacements.

As someone (citation needed) wisely said, try doesn't make it easier to handle errors. It makes it easier to not handle them.

If you analyse code and find lots of try replacements, all that tells you is that the code does nothing with errors except return them. That's probably not great code. Should we making it easier for people to be lazy and not worry about errors? Isn't that one reason Go doesn't have exceptions, to avoid precisely that?

davecheney commented 5 years ago

I'm not going to take a position on that. However, what I am finding is their little supporting evidence to suggest that

a. there are a lot of locations where try is applicable to existing go codebases b. error handling in general constitutes a significant portion of SLOC, based on my own measurements and the numbers mooted by the Go team, ref https://youtu.be/RIvL2ONhFBI?t=440 timecode 07:26

thomasf commented 5 years ago

If you analyse code and find lots of try replacements, all that tells you is that the code does nothing with errors except return them. That's probably not great code. Should we making it easier for people to be lazy and not worry about errors? Isn't that one reason Go doesn't have exceptions, to avoid precisely that?

  1. You are making value judgments about theoretical code you know nothing about which is not a good habit.
  2. I don't see how try is easier to misuse than what we have now, go has no features to enforce error handling and it's very easy to skip it already. For me try is about making code easier to read IF it does not need to handle errors.

Maybe there isn't a need for try because it won't bee needed in many places, but let's entertain the idea and come up with uses cases instead of just being grumpy about it...

I can't think of many practical situations myself how I would use it but it also does not exist yet so it's hard to tell if I would design some things differently if it did exist.. One thing that comes to mind is that I might do is group a number of actions in an anonymous function like this using try and still handle the error before returning it the the caller. It could make some code a lot more readable.

var v1, v3 string
if err := func() error {
    try(onething())
    v = try(twothing())
    try(otherthing())
    v3 = try(somethingg())
}(); err != nil {
  ... handle error...
}