Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Proposal: A built-in Go error check function, "try" #32437

Closed
griesemer opened this issue Jun 4, 2019 · 807 comments

Comments

@griesemer
Copy link
Contributor

@griesemer griesemer commented Jun 4, 2019

Proposal: A built-in Go error check function, try

This proposal has been closed. Thanks, everybody, for your input.

Before commenting, please read the detailed design doc and see the discussion summary as of June 6, the summary as of June 10, and most importantly the advice on staying focussed. Your question or suggestion may have already been answered or made. Thanks.

We propose a new built-in function called try, designed specifically to eliminate the boilerplate if statements typically associated with error handling in Go. No other language changes are suggested. We advocate using the existing defer statement and standard library functions to help with augmenting or wrapping of errors. This minimal approach addresses most common scenarios while adding very little complexity to the language. The try built-in is easy to explain, straightforward to implement, orthogonal to other language constructs, and fully backward-compatible. It also leaves open a path to extending the mechanism, should we wish to do so in the future.

[The text below has been edited to reflect the design doc more accurately.]

The try built-in function takes a single expression as argument. The expression must evaluate to n+1 values (where n may be zero) where the last value must be of type error. It returns the first n values (if any) if the (final) error argument is nil, otherwise it returns from the enclosing function with that error. For instance, code such as

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))

try can only be used in a function which itself returns an error result, and that result must be the last result parameter of the enclosing function.

This proposal reduces the original draft design presented at last year's GopherCon to its essence. If error augmentation or wrapping is desired there are two approaches: Stick with the tried-and-true if statement, or, alternatively, “declare” an error handler with a defer statement:

defer func() {
	if err != nil {	// no error may have occurred - check for it
		err = …	// wrap/augment error
	}
}()

Here, err is the name of the error result of the enclosing function. In practice, suitable helper functions will reduce the declaration of an error handler to a one-liner. For instance

defer fmt.HandleErrorf(&err, "copy %s %s", src, dst)

(where fmt.HandleErrorf decorates *err) reads well and can be implemented without the need for new language features.

The main drawback of this approach is that the error result parameter needs to be named, possibly leading to less pretty APIs. Ultimately this is a matter of style, and we believe we will adapt to expecting the new style, much as we adapted to not having semicolons.

In summary, try may seem unusual at first, but it is simply syntactic sugar tailor-made for one specific task, error handling with less boilerplate, and to handle that task well enough. As such it fits nicely into the philosophy of Go. try is not designed to address all error handling situations; it is designed to handle the most common case well, to keep the design simple and clear.

Credits

This proposal is strongly influenced by the feedback we have received so far. Specifically, it borrows ideas from:

Detailed design doc

https://github.com/golang/proposal/blob/master/design/32437-try-builtin.md

tryhard tool for exploring impact of try

https://github.com/griesemer/tryhard

@gopherbot gopherbot added this to the Proposal milestone Jun 4, 2019
@gopherbot gopherbot added the Proposal label Jun 4, 2019
@rasky

This comment has been minimized.

Copy link
Member

@rasky rasky commented Jun 4, 2019

I agree this is the best way forward: fixing the most common issue with a simple design.

I don't want to bikeshed (feel free to postpone this conversation), but Rust went there and eventually settled with the ? postfix operator rather than a builtin function, for increased readability.

The gophercon proposal cites ? in the considered ideas and gives three reason why it was discarded: the first ("control flow transfers are as a general rule accompanied by keywords") and the third ("handlers are more naturally defined with a keyword, so checks should too") do not apply anymore. The second is stylistic: it says that, even if the postfix operator works better for chaining, it can still read worse in some cases like:

check io.Copy(w, check newReader(foo))

rather than:

io.Copy(w, newReader(foo)?)?

but now we would have:

try(io.Copy(w, try(newReader(foo))))

which I think it's clearly the worse of the three, as it's not even obvious anymore which is the main function being called.

So the gist of my comment is that all three reasons cited in the gophercon proposal for not using ? do not apply to this try proposal; ? is concise, very readable, it does not obscure the statement structure (with its internal function call hierarchy), and it is chainable. It removes even more clutter from the view, while not obscuring the control flow more than the proposed try() already does.

@jimmyfrasche

This comment has been minimized.

Copy link
Member

@jimmyfrasche jimmyfrasche commented Jun 4, 2019

To clarify:

Does

func f() (n int, err error) {
  n = 7
  try(errors.New("x"))
  // ...
}

return (0, "x") or (7, "x")? I'd assume the latter.

Does the error return have to be named in the case where there's no decoration or handling (like in an internal helper function)? I'd assume not.

@ianlancetaylor

This comment has been minimized.

Copy link
Contributor

@ianlancetaylor ianlancetaylor commented Jun 5, 2019

Your example returns 7, errors.New("x"). This should be clear in the full doc that will soon be submitted (https://golang.org/cl/180557).

The error result parameter does not need to be named in order to use try. It only needs to be named if the function needs to refer to it in a deferred function or elsewhere.

@dominikh

This comment has been minimized.

Copy link
Member

@dominikh dominikh commented Jun 5, 2019

I am really unhappy with a built-in function affecting control flow of the caller. This is very unintuitive and a first for Go. I appreciate the impossibility of adding new keywords in Go 1, but working around that issue with magic built-in functions just seems wrong to me. It's worsened by the fact that built-ins can be shadowed, which drastically changes the way try(foo) behaves. Shadowing of other built-ins doesn't have results as unpredictable as control flow changing. It makes reading snippets of code without all of the context much harder.

I don't like the way postfix ? looks, but I think it still beats try(). As such, I agree with @rasky .

Edit: Well, I managed to completely forget that panic exists and isn't a keyword.

@griesemer

This comment has been minimized.

Copy link
Contributor Author

@griesemer griesemer commented Jun 5, 2019

The detailed proposal is now here (pending formatting improvements, to come shortly) and will hopefully answer a lot of questions.

@griesemer

This comment has been minimized.

Copy link
Contributor Author

@griesemer griesemer commented Jun 5, 2019

@dominikh The detailed proposal discusses this at length, but please note that panic and recover are two built-ins that affect control flow as well.

@nictuku

This comment has been minimized.

Copy link
Contributor

@nictuku nictuku commented Jun 5, 2019

One clarification / suggestion for improvement:

if the last argument supplied to try, of type error, is not nil, the enclosing function’s error result variable (...) is set to that non-nil error value before the enclosing function returns

Could this instead say is set to that non-nil error value and the enclosing function returns? (s/before/and)

On first reading, before the enclosing function returns seemed like it would eventually set the error value at some point in the future right before the function returned - possibly in a later line. The correct interpretation is that try may cause the current function to return. That's a surprising behavior for the current language, so a clearer text would be welcomed.

@purpleidea

This comment has been minimized.

Copy link

@purpleidea purpleidea commented Jun 5, 2019

I think this is just sugar, and a small number of vocal opponents teased golang about the repeated use of typing if err != nil ... and someone took it seriously. I don't think it's a problem. The only missing things are these two built-ins:

https://github.com/purpleidea/mgmt/blob/a235b760dc3047a0d66bb0b9d63c25bc746ed274/util/errwrap/errwrap.go#L26

@webermaster

This comment has been minimized.

Copy link

@webermaster webermaster commented Jun 5, 2019

Not sure why anyone ever would write a function like this but what would be the envisioned output for

try(foobar())

If foobar returned (error, error)

@dominikh

This comment has been minimized.

Copy link
Member

@dominikh dominikh commented Jun 5, 2019

I retract my previous concerns about control flow and I no longer suggest using ?. I apologize for the knee-jerk response (though I'd like to point out this wouldn't have happened had the issue been filed after the full proposal was available).

I disagree with the necessity for simplified error handling, but I'm sure that is a losing battle. try as laid out in the proposal seems to be the least bad way of doing it.

@ianlancetaylor

This comment has been minimized.

Copy link
Contributor

@ianlancetaylor ianlancetaylor commented Jun 5, 2019

@webermaster Only the last error result is special for the expression passed to try, as described in the proposal doc.

@akyoto

This comment has been minimized.

Copy link
Contributor

@akyoto akyoto commented Jun 5, 2019

Like @dominikh, I also disagree with the necessity of simplified error handling.

It moves vertical complexity into horizontal complexity which is rarely a good idea.

If I absolutely had to choose between simplifying error handling proposals, though, this would be my preferred proposal.

@cespare

This comment has been minimized.

Copy link
Contributor

@cespare cespare commented Jun 5, 2019

It would be helpful if this could be accompanied (at some stage of accepted-ness) by a tool to transform Go code to use try in some subset of error-returning functions where such a transformation can be easily performed without changing semantics. Three benefits occur to me:

  • When evaluating this proposal, it would allow people to quickly get a sense for how try could be used in their codebase.
  • If try lands in a future version of Go, people will likely want to change their code to make use of it. Having a tool to automate the easy cases will help a lot.
  • Having a way to quickly transform a large codebase to use try will make it easy to examine the effects of the implementation at scale. (Correctness, performance, and code size, say.) The implementation may be simple enough to make this a negligible consideration, though.
@lestrrat

This comment has been minimized.

Copy link

@lestrrat lestrrat commented Jun 5, 2019

I just would like to express that I think a bare try(foo()) actually bailing out of the calling function takes away from us the visual cue that function flow may change depending on the result.

I feel I can work with try given enough getting used, but I also do feel we will need extra IDE support (or some such) to highlight try to efficiently recognize the implicit flow in code reviews/debugging sessions

@Goodwine

This comment has been minimized.

Copy link

@Goodwine Goodwine commented Jun 5, 2019

The thing I'm most concerned about is the need to have named return values just so that the defer statement is happy.

I think the overall error handling issue that the community complains about is a combination of the boilerplate of if err != nil AND adding context to errors. The FAQ clearly states that the latter is left out intentionally as a separate problem, but I feel like then this becomes an incomplete solution, but I'll be willing to give it a chance after thinking on these 2 things:

  1. Declare err at the beginning of the function.
    Does this work? I recall issues with defer & unnamed results. If it doesn't the proposal needs to consider this.
func sample() (string, error) {
  var err error
  defer fmt.HandleErrorf(&err, "whatever")
  s := try(f())
  return s, nil
}
  1. Assign values like we did in the past, but use a helper wrapf function that has the if err != nil boilerplate.
func sample() (string, error) {
  s, err := f()
  try(wrapf(err, "whatever"))
  return s, nil
}
func wrapf(err error, format string, ...v interface{}) error {
  if err != nil {
    // err = wrapped error
  }
  return err
}

If either work, I can deal with it.

@randall77

This comment has been minimized.

Copy link
Contributor

@randall77 randall77 commented Jun 5, 2019

func sample() (string, error) {
  var err error
  defer fmt.HandleErrorf(&err, "whatever")
  s := try(f())
  return s, nil
}

This will not work. The defer will update the local err variable, which is unrelated to the return value.

func sample() (string, error) {
  s, err := f()
  try(wrapf(err, "whatever"))
  return s, nil
}
func wrapf(err error, format string, ...v interface{}) error {
  if err != nil {
    // err = wrapped error
  }
  return err
}

That should work. It will call wrapf even on a nil error, though.
This will also (continue to) work, and is IMO a lot clearer:

func sample() (string, error) {
  s, err := f()
  if err != nil {
      return "", wrap(err)
  }
  return s, nil
}

No one is going to make you use try.

@singhpradeep

This comment has been minimized.

Copy link

@singhpradeep singhpradeep commented Jun 5, 2019

Not sure why anyone ever would write a function like this but what would be the envisioned output for

try(foobar())

If foobar returned (error, error)

Why would you return more than one error from a function? If you are returning more than one error from function, perhaps function should be split into two separate ones in the first place, each returning just one error.

Could you elaborate with an example?

@griesemer

This comment has been minimized.

Copy link
Contributor Author

@griesemer griesemer commented Jun 5, 2019

@cespare: It should be possible for somebody to write a go fix that rewrites existing code suitable for try such that it uses try. It may be useful to get a feel for how existing code could be simplified. We don't expect any significant changes in code size or performance, since try is just syntactic sugar, replacing a common pattern by a shorter piece of source code that produces essentially the same output code. Note also that code that uses try will be bound to use a Go version that's at least the version at which try was introduced.

@lestrrat: Agreed that one will have to learn that try can change control flow. We suspect that IDE's could highlight that easily enough.

@Goodwine: As @randall77 already pointed out, your first suggestion won't work. One option we have thought about (but not discussed in the doc) is the possibility of having some predeclared variable that denotes the error result (if one is present in the first place). That would eliminate the need for naming that result just so it can be used in a defer. But that would be even more magic; it doesn't seem justified. The problem with naming the return result is essentially cosmetic, and where that matters most is in the auto-generated APIs served by go doc and friends. It would be easy to address this in those tools (see also the detailed design doc's FAQ on this subject).

@griesemer

This comment has been minimized.

Copy link
Contributor Author

@griesemer griesemer commented Jun 5, 2019

@nictuku: Regarding your suggestion for clarification (s/before/and/): I think the code immediately before the paragraph you're referring to makes it clear what happens exactly, but I see your point, s/before/and/ may make the prose clearer. I'll make the change.

See CL 180637.

@deanveloper

This comment has been minimized.

Copy link

@deanveloper deanveloper commented Jun 5, 2019

I actually really like this proposal. However, I do have one criticism. The exit point of functions in Go have always been marked by a return. Panics are also exit points, however those are catastrophic errors that are typically not meant to ever be encountered.

Making an exit point of a function that isn't a return, and is meant to be commonplace, may lead to much less readable code. I had heard about this in a talk and it is hard to unsee the beauty of how this code is structured:

func CopyFile(src, dst string) error {
	r, err := os.Open(src)
	if err != nil {
		return fmt.Errorf("copy %s %s: %v", src, dst, err)
	}
	defer r.Close()

	w, err := os.Create(dst)
	if err != nil {
		return fmt.Errorf("copy %s %s: %v", src, dst, err)
	}

	if _, err := io.Copy(w, r); err != nil {
		w.Close()
		os.Remove(dst)
		return fmt.Errorf("copy %s %s: %v", src, dst, err)
	}

	if err := w.Close(); err != nil {
		os.Remove(dst)
		return fmt.Errorf("copy %s %s: %v", src, dst, err)
	}
}

This code may look like a big mess, and was meant to by the error handling draft, but let's compare it to the same thing with try.

func CopyFile(src, dst string) error {
	defer func() {
		err = fmt.Errorf("copy %s %s: %v", src, dst, err)
	}()
	r, err := try(os.Open(src))
	defer r.Close()

	w, err := try(os.Create(dst))

	defer w.Close()
	defer os.Remove(dst)
	try(io.Copy(w, r))
	try(w.Close())

	return nil
}

You may look at this at first glance and think it looks better, because there is a lot less repeated code. However, it was very easy to spot all of the spots that the function returned in the first example. They were all indented and started with return, followed by a space. This is because of the fact that all conditional returns must be inside of conditional blocks, thereby being indented by gofmt standards. return is also, as previously stated, the only way to leave a function without saying that a catastrophic error occurred. In the second example, there is only a single return, so it looks like the only thing that the function ever should return is nil. The last two try calls are easy to see, but the first two are a bit harder, and would be even harder if they were nested somewhere, ie something like proc := try(os.FindProcess(try(strconv.Atoi(os.Args[1])))).

Returning from a function has seemed to have been a "sacred" thing to do, which is why I personally think that all exit points of a function should be marked by return.

@s4n-gt

This comment has been minimized.

Copy link

@s4n-gt s4n-gt commented Jun 5, 2019

Someone has already implemented this 5 years ago. If you are interested, you can
try this feature

https://news.ycombinator.com/item?id=20101417

I 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

Here are some examples of me using it in error-check-heavy functions: https://github.com/lunixbochs/poxd/blob/master/tls.go#L13

@jasonmoo

This comment has been minimized.

Copy link

@jasonmoo jasonmoo commented Jun 5, 2019

I appreciate the effort that went into this. I think it's the most go-ey solution I've seen so far. But I think it introduces a bunch of work when debugging. Unwrapping try and adding an if block every time I debug and rewrapping it when I'm done is tedious. And I also have some cringe about the magical err variable that I need to consider. I've never been bothered by the explicit error checking so perhaps I'm the wrong person to ask. It always struck me as "ready to debug".

@Goodwine

This comment has been minimized.

Copy link

@Goodwine Goodwine commented Jun 5, 2019

@griesemer
My problem with your proposed use of defer as a way to handle the error wrapping is that the behavior from the snippet I showed (repeated below) is not very common AFAICT, and because it's very rare then I can imagine people writing this thinking it works when it doesn't.

Like.. a beginner wouldn't know this, if they have a bug because of this they won't go "of course, I need a named return", they would get stressed out because it should work and it doesn't.

var err error
defer fmt.HandleErrorf(err);

try is already too magic so you may as well go all the way and add that implicit error value. Think on the beginners, not on those who know all the nuances of Go. If it's not clear enough, I don't think it's the right solution.

Or... Don't suggest using defer like this, try another way that's safer but still readable.

@griesemer

This comment has been minimized.

Copy link
Contributor Author

@griesemer griesemer commented Jun 5, 2019

@deanveloper It is true that this proposal (and for that matter, any proposal trying to attempt the same thing) will remove explicitly visible return statements from the source code - that is the whole point of the proposal after all, isn't it? To remove the boilerplate of if statements and returns that are all the same. If you want to keep the return's, don't use try.

We are used to immediately recognize return statements (and panic's) because that's how this kind of control flow is expressed in Go (and many other languages). It seems not far fetched that we will also recognize try as changing control flow after some getting used to it, just like we do for return. I have no doubt that good IDE support will help with this as well.

@buchanae

This comment has been minimized.

Copy link
Contributor

@buchanae buchanae commented Jun 5, 2019

I have two concerns:

  • named returns have been very confusing, and this encourages them with a new and important use case
  • this will discourage adding context to errors

In my experience, adding context to errors immediately after each call site is critical to having code that can be easily debugged. And named returns have caused confusion for nearly every Go developer I know at some point.

A more minor, stylistic concern is that it's unfortunate how many lines of code will now be wrapped in try(actualThing()). I can imagine seeing most lines in a codebase wrapped in try(). That feels unfortunate.

I think these concerns would be addressed with a tweak:

a, b, err := myFunc()
check(err, "calling myFunc on %v and %v", a, b)

check() would behave much like try(), but would drop the behavior of passing through function return values generically, and instead would provide the ability to add context. It would still trigger a return.

This would retain many of the advantages of try():

  • it's a built-in
  • it follows the existing control flow WRT to defer
  • it aligns with existing practice of adding context to errors well
  • it aligns with current proposals and libraries for error wrapping, such as errors.Wrap(err, "context message")
  • it results in a clean call site: there's no boilerplate on the a, b, err := myFunc() line
  • describing errors with defer fmt.HandleError(&err, "msg") is still possible, but doesn't need to be encouraged.
  • the signature of check is slightly simpler, because it doesn't need to return an arbitrary number of arguments from the function it is wrapping.
@griesemer

This comment has been minimized.

Copy link
Contributor Author

@griesemer griesemer commented Jun 5, 2019

@s4n-gt Thanks for this link. I was not aware of it.

@griesemer

This comment has been minimized.

Copy link
Contributor Author

@griesemer griesemer commented Jun 5, 2019

@Goodwine Point taken. The reason for not providing more direct error handling support is discussed in the design doc in detail. It is also a fact that over the course of a year or so (since the draft designs published at last year's Gophercon) no satisfying solution for explicit error handling has come up. Which is why this proposal leaves this out on purpose (and instead suggests to use a defer). This proposal still leaves the door open for future improvements in that regard.

@josharian

This comment has been minimized.

Copy link
Contributor

@josharian josharian commented Jun 5, 2019

The proposal mentions changing package testing to allow tests and benchmarks to return an error. Though it wouldn’t be “a modest library change”, we could consider accepting func main() error as well. It’d make writing little scripts much nicer. The semantics would be equivalent to:

func main() {
  if err := newmain(); err != nil {
    println(err.Error())
    os.Exit(1)
  }
}
@griesemer

This comment has been minimized.

Copy link
Contributor Author

@griesemer griesemer commented Jul 18, 2019

First of all, thanks everybody for the supportive feedback on the final decision, even if that decision was not satisfactory for many. This was truly a team effort, and I'm really happy that we all managed to get through the intense discussions in an overall civil and respectful way.

@ngrilly Speaking just for myself, I still think it would be nice to address error handling verbosity at some point. That said, we have just dedicated quite a bit of time and energy on this over the last half year and especially the last 3 months, and we were quite happy with the proposal, yet we have obviously underestimated the possible reaction towards it. Now it does make a lot of sense to step back, digest and distill the feedback, and then decide on the best next steps.

Also, realistically, since we don't have unlimited resources, I see thinking about language support for error handling go on the back-burner for a bit in favor of more progress on other fronts, most notably work on generics, at least for the next few months. if err != nil may be annoying, but it's not a reason for urgent action.

If you like to continue the discussion, I would like to gently suggest to everybody to move off from here and continue the discussion elsewhere, in a separate issue (if there's a clear proposal), or in other forums better suited for an open discussion. This issue is closed, after all. Thanks.

@mirtchovski

This comment has been minimized.

Copy link
Contributor

@mirtchovski mirtchovski commented Jul 19, 2019

I'm afraid there is a self-selection bias.

I'd like to coin a new term here and now: "creator bias". If someone is willing to put the work, they should be given the benefit of the doubt.

It's very easy for the peanut gallery to shout loud and wide on unrelated forums how they dislike a proposed solution to a problem. It's also very easy for everyone to write a 3-paragraph incomplete attempt for a different solution (with no real work presented on the sideline). If one agrees with the status quo, ok. Fair point. Presenting anything else as a solution without a complete prposal gives you -10k points.

@dataf3l

This comment has been minimized.

Copy link

@dataf3l dataf3l commented Jul 19, 2019

I don't support or am against try, but I trust Go Teams' judgement on the matter, so far their judgement has provided with an excellent language, so I think whatever they decide will work for me, try or no try, I consider we need to understand as outsiders, that the maintainers have broader visibility over the matter. syntax we can discuss all day. I'd like to thank everyone who has worked on or is trying to improve go at the moment for their efforts, we are thankful and look forward for new (non-backwards-breaking) improvements in the language libraries and Runtime if any is deemed useful by you guys.

@lpar

This comment has been minimized.

Copy link

@lpar lpar commented Jul 19, 2019

It's also very easy for everyone to write a 3-paragraph incomplete attempt for a different solution (with no real work presented on the sideline).

The only thing I (and a number of others) wanted to make try useful was an optional argument to allow it to return a wrapped version of the error instead of the unchanged error. I don't think that needed a huge amount of design work.

@ab-pivot

This comment has been minimized.

Copy link

@ab-pivot ab-pivot commented Jul 19, 2019

Oh no.

@mrdulin

This comment has been minimized.

Copy link

@mrdulin mrdulin commented Jul 21, 2019

I see. Go want to make something different from other languages.

@simskij

This comment has been minimized.

Copy link

@simskij simskij commented Jul 21, 2019

Maybe someone should lock this issue? The discussion is probably better suited elsewhere.

@ianlancetaylor

This comment has been minimized.

Copy link
Contributor

@ianlancetaylor ianlancetaylor commented Jul 21, 2019

This issue is already so long that locking it seems pointless.

Everyone, please be aware that this issue is closed, and the comments you make here will almost certainly be ignored forever. If that is OK with you, comment away.

@OuSatoru

This comment has been minimized.

Copy link

@OuSatoru OuSatoru commented Jul 22, 2019

In case someone hate the try word which let them think of the Java, C* language, I advice not to use 'try' but other words like 'help' or 'must' or 'checkError'.. (ignore me)

@thomasf

This comment has been minimized.

Copy link

@thomasf thomasf commented Jul 22, 2019

In case someone hate the try word which let them think of the Java, C* language, I advice not to use 'try' but other words like 'help' or 'must' or 'checkError'.. (ignore me)

There will always be overlapping keywords and concepts which have small or large semantic differences in languages which are reasonably near each other (like C-family languages). A language feature should not cause confusion inside the language itself, differences between languages will always happen.

@alersenkevich

This comment has been minimized.

Copy link

@alersenkevich alersenkevich commented Jul 25, 2019

bad. this is anti pattern, disrespect author of that proposal

@ianlancetaylor

This comment has been minimized.

Copy link
Contributor

@ianlancetaylor ianlancetaylor commented Jul 25, 2019

@alersenkevich Please be polite. Please see https://golang.org/conduct.

@Chillance

This comment has been minimized.

Copy link

@Chillance Chillance commented Jul 27, 2019

I think I'm glad about the decision to not go further with this. To me this felt like a quick hack to solve a small issue regarding if err != nil being on multiple lines. We don't want to bloat Go with minor keywords to solve minor things like this do we? This is why the proposal with hygienic macros #32620 feels better. It tries to be a more generic solution to open up more flexibility with more things. Syntax and usage discussion ongoing there, so don't just think if it being C/C++ macros. The point there is to discuss a better way to do macros. With it, you could implement your own try.

@mvndaai

This comment has been minimized.

Copy link

@mvndaai mvndaai commented Jul 29, 2019

I would love feedback on a similar proposal that addresses a problem with current error handling #33161.

@OneOfOne

This comment has been minimized.

Copy link
Contributor

@OneOfOne OneOfOne commented Jul 30, 2019

Honestly this should be reopened, out of all the err handling proposals, this is the most sane one.

@RobertGrantEllis

This comment has been minimized.

Copy link

@RobertGrantEllis RobertGrantEllis commented Jul 30, 2019

@OneOfOne respectfully, I disagree that this should be reopened. This thread has established that there are real limitations with the syntax. Perhaps you are right that this is the most "sane" proposal: but I believe that the status quo is more sane still.

I agree that if err != nil is written far too often in Go- but having a singular way to return from a function hugely improves readability. While I can generally get behind proposals that reduce boilerplate code, the cost should never be readability IMHO.

I know a lot of developers lament the "longhand" error checking in go, but honestly terseness is often at odds with readability. Go has many established patterns here and elsewhere that encourage a particular way of doing things, and, in my experience, the result is reliable code that ages well. This is critical: real-world code has to be read and understood many times throughout its lifetime, but is only ever written once. Cognitive overhead is a real cost, even for experienced developers.

@florisvdg

This comment has been minimized.

Copy link

@florisvdg florisvdg commented Sep 13, 2019

Instead of:

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

I'd expect:

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

This comment has been minimized.

Copy link

@bitfield bitfield commented Sep 13, 2019

Everyone, please be aware that this issue is closed, and the comments you make here will almost certainly be ignored forever. If that is OK with you, comment away.
@ianlancetaylor

@aminsol

This comment has been minimized.

Copy link

@aminsol aminsol commented Oct 26, 2019

It would be nice If we could use try for a block of codes alongside with the current way of handling errors.
Something like this:

// Generic Error Handler
handler := func(err error) error {
    return fmt.Errorf("We encounter an error: %v", err)  
}
a := "not Integer"
b := "not Integer"

try(handler){
    f := os.Open(filename)
    x := strconv.Atoi(a)
    y, err := strconv.Atoi(b) // <------ If you want a specific error handler
    if err != nil {
        panic("We cannot covert b to int")   
    }
}

The code above seems cleaner than the initial comment. I wish I could purpose this.

I made a new proposal #35179

@NareshDen

This comment has been minimized.

Copy link

@NareshDen NareshDen commented Nov 2, 2019

val := try f() (err){
panic(err)
}

@myroid

This comment has been minimized.

Copy link

@myroid myroid commented Nov 8, 2019

I hope so:

i, err := strconv.Atoi("1")
if err {
    println("ERROR")
} else {
    println(i)
}

or

i, err := strconv.Atoi("1")
if err {
    io.EOF:
        println("EOF")
    io.ErrShortWrite:
        println("ErrShortWrite")
} else {
    println(i)
}
@piotrkowalczuk

This comment has been minimized.

Copy link

@piotrkowalczuk piotrkowalczuk commented Nov 8, 2019

@myroid I wouldn't mind having your second example made a little bit more generic in a form of switch-else statement:

i, err := strconv.Atoi("1")
switch err != nil; err {
case io.EOF:
        println("EOF")
case io.ErrShortWrite:
        println("ErrShortWrite")
} else {
    println(i)
}
@myroid

This comment has been minimized.

Copy link

@myroid myroid commented Nov 8, 2019

@piotrkowalczuk Your code looks much better than mine. I think the code can be more concise.

i, err := strconv.Atoi("1")
switch err {
case io.EOF:
    println("EOF")
case io.ErrShortWrite:
    println("ErrShortWrite")
} else {
    println(i)
}
@guybrand

This comment has been minimized.

Copy link

@guybrand guybrand commented Nov 8, 2019

@tv42

This comment has been minimized.

Copy link

@tv42 tv42 commented Nov 8, 2019

switch doesn't need an else, it has default.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
You can’t perform that action at this time.