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: Go 2: on err or catch statement for error handling #32611

Closed
networkimprov opened this issue Jun 13, 2019 · 94 comments
Closed

proposal: Go 2: on err or catch statement for error handling #32611

networkimprov opened this issue Jun 13, 2019 · 94 comments
Labels
error-handling Language & library change proposals that are about error handling. FrozenDueToAge LanguageChange Suggested changes to the Go language Proposal v2 An incompatible library change
Milestone

Comments

@networkimprov
Copy link

networkimprov commented Jun 13, 2019

The try() proposal #32437 has met with significant complaint, especially about
a) lack of support for decorating/wrapping returned errors
b) lack of support for widespread alternatives to returning error, e.g. log.Fatal(), retry, etc.
c) obscured function exit
d) difficulty of inserting debugging logic

If a separate line is usually required for decoration/wrap, let's just "allocate" that line.

f, err := os.Open(path)  // normal Go \o/
on err, return fmt.Errorf("Cannot open %s, due to %v", path, err)

(This may overlap too much with if ... { <stmt> }. An error-specific variant is given in Critiques no. 1.)

This plugs easily into existing code, as it leaves the err variable intact for any subsequent use.

This supports any single-statement action, and can be extended with named handlers:

err := f() // followed by one of

on err, return err            // any type can be tested for non-zero
on err, return fmt.Errorf(...)

on err, fmt.Println(err)      // doesn't stop the function
on err, continue              // retry in a loop

on err, goto label            // labeled handler invocation
on err, hname                 // named handler invocation
on err, ignore                // logs error if handle ignore() defined

handle hname(clr caller) {    // type caller has results of runtime.Caller()
   if err == io.Bad { return err } // non-local return
   fmt.Println(clr, err)
}

Keyword on borrows from Javascript. It's preferrable not to overload if.
A comma isn't essential, but semicolon doesn't seem right. Maybe colon?

Go 1 boilerplate is 20 characters on 3 lines: if err != nil {\n\t\n}\n
This boilerplate is 9 characters on 1 line: on err, \n

Specifics

// Proposed
on expression, statement

// Equivalent Go
var zero_value T // global, not on stack
if expression != zero_value {
   statement
}

// Disallowed; these require a named handler or function call
on expression, if/for/switch/func { ... }
on expression, { statement; statement }

The expression is constrained to ensure that its use (if any) within the statement is correct. For example, expression cannot be a function call or channel receive. It could be limited to an lvalue, literal, or constant,

Possible Extensions

For an assignment or short declaration with a single lvalue, on could test that value. (Note, I don't recommend this; better to have on err consistently on its own line.)

on err := f(), <stmt>

An error inspection feature would be helpful...

on err, os.IsNotExist(err):  <stmt>
on err, err == io.EOF:       <stmt>
on err, err.(*os.PathError): <stmt>    // doesn't panic if not a match

on err, <condition>: <stmt>
on err: <stmt>              // this pair provides if/else in 2 lines

A condition list suggests parentheses:

on (err) <stmt>
on (err, <condtion>) <stmt>

Critiques

  1. The above may be too general-purpose. A post-assignment alternative is:

    err := f()
    catch <stmt>                  // test last var in preceding assignment for non-zero
    catch (err == io.EOF) <stmt>  // test last var and boolean condition
    catch (ep, ok := err.(*os.PathError); ok) <stmt>  // assignment preceding condition
    
  2. This doesn't accommodate narrowly scoped error variables if err := f(); err != nil { ... }. A way to scope the variable to the stmt is:

    _ = f()
    catch err, <stmt>
    catch err, (err != io.EOF) <stmt>
    
  3. go fmt could just allow single-line if statements:
    Would it also allow single-line case, for, else, var () ? I'd like them all, please ;-)
    The Go team has turned aside many requests for single-line error checks.

  4. It's as repetitive as the current idiom:
    on err, return err statements may be repetitive, but they're explicit, terse, and clear.

@gopherbot add Go2, LanguageChange

@gopherbot gopherbot added this to the Proposal milestone Jun 13, 2019
@gopherbot gopherbot added Proposal v2 An incompatible library change LanguageChange Suggested changes to the Go language labels Jun 13, 2019
@danrl
Copy link

danrl commented Jun 13, 2019

I was just thinking how inlining a handler would look like.

on err, { 
  do()
  things()
  return err
}

The comma doesn't feel right in that case.
This makes me think if using curly brackets instead of the comma is an option. However, the code then becomes more like the boilerplate that we have already with if.

@gertcuykens
Copy link
Contributor

gertcuykens commented Jun 13, 2019

I would opt for a ,= reverse nil operator that does the same but keeps the clean line of side

err := f() // followed by one of
err ,= return err            // any type can be tested for non-zero
err ,= return fmt.Errorf(...)
err ,= fmt.Println(err)      // doesn't stop the function
err ,= continue              // retry in a loop
err ,= hname err             // named handler invocation without parens
err ,= ignore err            // logs error if handle ignore() defined
err ,= { 
   do()
   things()
   return err
}
handle hname(err error, clr caller) { // type caller has results of runtime.Caller()
   if err == io.Bad { return err } // non-local return
   fmt.Println(clr, err)
}

@networkimprov
Copy link
Author

/cc @daved @brynbellomy @guybrand

@guybrand
Copy link

guybrand commented Jun 13, 2019

@networkimprov

on err, return err // any type can be tested for non-zero

The comma between "on err, return" bugs me, as comma is most commonly used between two variables.
it doesnt look much like go syntax

  1. Quoting @griesemer from Proposal: A built-in Go error check function, "try" #32437 (comment)
    (And was also the approach on the try() proposal itself) :
    "minimal idea which addresses one part of error handling well"

I think the above proposal offers a wide variety of solutions as you stated:

The try() proposal #32437 has met with significant complaint, especially about
a) decorating/wrapping returned errors, and
b) lack of support for widespread alternatives to returning error, e.g. log.Fatal(), retry, etc.

And even some more, but I'm not sure there's a bandwidth to address it, I tried to advocate a rating mechanism for try() or any other proposal (BTW, can be a good concept not only for the Go 2 error handling improvements but a general cost-effective per major development effort criteria), but I dont see the concept is well understood, If there's no bandwidth the options are:
a. Narrow down the proposal to be "affordable" - and try to compromise as little as possible.
b. Perfect it as much as possible, so perhaps one day when it will be affordable it would be a lead option
c. break it down to sub requests so perhaps it can be implemented in steps.

@alexhornbake
Copy link

alexhornbake commented Jun 14, 2019

@networkimprov I agree a conditional return without assignment (ie, not try) on it's own line make a lot of sense, and seems to strike a good balance between being clear, and convenient.

, bugs me as well, and is a new meaning for the seperator.
on <x> == if <x> != nil does not seem like the best meaning for the word "on"

What if it was 1 line if statements with semicolon as the seperator? (not sure how this jives with the lexer)

if err != nil; return err
if err != nil; return fmt.Errorf(...)

There is already a history of using ; in a similar way

if thing, err := doThing(); err != nil {
...
}

@rsc
Copy link
Contributor

rsc commented Jun 14, 2019

This proposal is missing the "precise statement of the proposed change" part.
It is difficult to infer that precise statement from examples.
As best I can tell, the proposal is to add

on X, Y

where X must have type error, and which expands to

if X != nil { Y }

Is that a correct and complete statement of what you are proposing?

@networkimprov
Copy link
Author

networkimprov commented Jun 14, 2019

@rsc, thanks for pointing that out. I've added a Specifics section, which says

// Proposed
on expression, statement

// Equivalent Go
var zero_value T
if expression != zero_value {
   statement
}

The expression is constrained to ensure that its use (if any) within the statement is correct. For example, expression cannot be a function call or channel receive. It could be limited to an lvalue, literal, or constant,

IOW, it's not specific to type error.

EDIT: I've also added a Possible Extensions section.

@dpinela
Copy link
Contributor

dpinela commented Jun 15, 2019

As you hinted at yourself, the proposed syntax doesn't gain much over just putting the equivalent if statement in one line:

if err != nil { return err }
on err, return err

I don't think it's worth making a breaking change to the language for something you can almost get with a gofmt tweak.

@JAicewizard
Copy link

I don't get the point of "but if we have on line if-statements". We do now have them, and AFAIK the go team doesn't want them. We don't have them for a reason. If we add them that would pretty much nullify this proposal, but that doesn't mean that this proposal is any worse because of it.
this has a clear advantage over on-line if-statements, it is very clear what is going to happen, and is less general then an if-statement. You cant just use this wherever.

@peterbourgon
Copy link

I support this proposal.

It is important that any change to error handling allows each error in a function to be annotated explicitly and independently from other errors. Returning the naked error, or using the same annotation for every error return in a function, may be useful in some exceptional circumstances, but are not good general practices.

I also support using a keyword instead of a builtin. If we add a new way to exit a function, it is important that it isn't nestable within other expressions. If we have to wait until further improvements to the language allow this to be accomplished without breaking the Go 1 compatibility promise, then we should do that, rather than make the change now.

@griesemer
Copy link
Contributor

@peterbourgon Can you explain why you think on err is so much better than if err != nil that we need new syntax?

Also, there is a mantra that every error needs to be decorated somehow. I agree that this is important at the library boundary level. But there are plenty of package interior functions where decorating an error is not needed; in fact it only leads to over-decorated errors. Can you expand on the statement of bad practice that you are referring to?

@peterbourgon
Copy link

peterbourgon commented Jun 30, 2019

@griesemer

Can you explain why you think on err is so much better than if err != nil that we need new syntax?

To be clear, I'm in the "leave if err != nil alone" camp, like many other veterans of the language. But it seems like the core team has decided this is a "problem" that's worth solving, and I have sympathy for the perspective that it might encourage more not-yet-Gophers to become Gophers, so I'm speaking from that assumption.

edit: From that assumption, I think it's important that any proposal not affect the semantics (for lack of a better word) of error handling as it exists today (by which I mean that errors are generally totally handled adjacent to the error-causing expression) and instead affect only the verbosity of error handling. And the only commonality between the error handling blocks I write which could be reduced are

if err != nil { // <-- this line
    ...         //
    return ...  // <-- this keyword
}               // <-- and this line

For that reason, I feel like on is about as good as it can get, I guess.

Also, there is a mantra that every error needs to be decorated somehow. I agree that this is important at the library boundary level. But there are plenty of package interior functions where decorating an error is not needed; in fact it only leads to over-decorated errors. Can you expand on the statement of bad practice that you are referring to?

I think this is one of many points that demonstrate a disconnect between the core Go team and Go as it is used in practice, at least as I have always experienced it. Over the years I've taught dozens Go courses, consulted for nearly a hundred companies, small and large, startups and Fortune-500; I suspect I've interacted with thousands of Go programmers, and seen nearly as many discrete Go codebases. They are almost exclusively private, and their developers infrequently respond to surveys. As I see it, this is the primary market for Go the language. (Of course, I may be wrong. Grain of salt and all.)

In these contexts there are almost no packages which are imported by more than one binary. In fact I'd estimate that ~50% of Go code I've seen has just been in package main. These developers rarely have enough time or experience to properly structure the main business logic of their programs, much less think deeply about the structure or cleanliness of their errors. Many, many developers have the instinct to write functions like this, and use them everywhere:

func checkErr(err error) {
    if err != nil {
        panic(err)
    }
}

So, given this reality, where package boundaries are rarely well-defined, where functions instead serve as the primary way to hew borders in programs, the risk is never over-decoration. I've never once given a code review where I requested the author to de-annotate an error because it was too noisy. But I've given plenty of reviews where I've had to convince the author to remove their checkErr helper or equivalent and actually return errors at all rather than just terminating the process from somewhere deep in the callstack. So if a new language feature provides most of the "I don't have to think about this" convenience of checkErr and requires something like a deferred block to do annotation, I'm positive that almost no developer as I've described them will perform any annotation whatsoever, and I fear we'll move the resiliency and coherence of most Go codebases backwards, not forwards.

Of course, on err allows this to happen, too, but at least in code review we can suggest in-line amendments, which I believe would be more eagerly adopted.

@griesemer
Copy link
Contributor

Thanks, @peterbourgon for your detailed comment. We've picked up error checking because the community has repeatedly and strongly complained about the verbosity of error handling. And yes, we also think that it might be nice if there were a less verbose approach where possible. So we're trying to move this forward if we can.

From what you're saying, try would already be a big step up from checkErr - for one it takes care of dealing with an error in the first place, and it also promotes at least an error return rather than just a panic. Because it removes the need for a checkErr function and extra call on a separate line, I'd expect those checkErr programmers to flock to try.

I see your point that on err might be easier to change into proper error handling (i.e., decoration), but again, judging from the reality you are describing it seems that checkErr would still be the much easier way out. If these programmers have rarely enough time or experience to do error handling "properly", why not provide a tool that gives them a strong incentive to check and forward errors? And rather than insist on decorating each error separately, use a deferred error handler (which can be one line)? This seems like a much easier sell than having them replace checkErr with an on err statement.

(As an aside, I've written plenty of little tools that just panic when I don't care about the error - not great, but good enough when time is of the essence. try would make my life much easier and allow me to do something better than panic.)

@networkimprov
Copy link
Author

@peterbourgon @griesemer, the last few comments haven't considered that on err provides handling of recoverable errors; not just ones that return. These are very common, and try is no help to them.

@networkimprov
Copy link
Author

I've added error inspection to the Possible Extensions section:

on err, os.IsNotExist(err):  <stmt>
on err, err == io.EOF:       <stmt>
on err, err.(*os.PathError): <stmt>    // doesn't panic if not a match

@ulikunitz
Copy link
Contributor

Does this proposal reinvent C semantics for if and calls it on? The statement is executed if the expression is non-zero. The two differences are that C allows compound statements (aka blocks) and supports additionally an else branch.

@ohir
Copy link

ohir commented Jul 2, 2019

If we're at a new keyword, why not generalize it as an "alias" to the
if var != zero_value { block }. Current whines about 'verbosity of error handling' IMO stem mostly not from the language spec per se but from the parser shortcut then gofmt rules that make simple if three lines high (whats not that bad as the stuttering != nil ;).

"on" statements specify the conditional execution of a single branch according to the immediate result of a boolean expression, or a boolean result of an implicit comparison of the non-boolean value of an expression with the zero value of its type. If the result of either is true, the "on" branch is executed, otherwise it is not executed.

OnStmt = "on" [ SimpleStmt ";" ] Expression Block.

on err {
    return err
}
on !err {"\n"}
on ok := call(); ok {"\n"}
on ok := call(); !ok {"\n"}
on !(x%d) {"\n"}
on a - b {"\n"}

Then after a slight reduction, we may just add this pinch of implicit nonmagic
to the "if" itself: "if the Expression value is not of boolean type...".

Magic semicolon rules, with the closing parenthesis relaxing rule are more magic than that.

if err { 
    return err
}
if !err {"\n"}
if ok := call(); ok {"\n"}
if ok := call(); !ok {"\n"}
if !(x%d) {"\n"}
if a - b {"\n"}

The real change would be to allow single statement blocks to occupy a line. But in fact I now value that space hinting at the possible change of the control flow.

@ulikunitz Yes, indeed :)

@ianlancetaylor
Copy link
Contributor

I think the boilerplate that counts is what people see.

This reduces three lines to one line.

It reduces if err != nil {} to on err,, which by my count is reducing 12 characters to 6.

This adds a new kind of constrained expression which does not exist elsewhere in the language. The suggested extensions seem to mostly try to unconstrain the expression a bit by writing || as ,.

To me personally I don't think the savings are enough to justify the language change.

@ohir
Copy link

ohir commented Jul 2, 2019

@ianlancetaylor

the savings are enough to justify the language change.

For me personally with try there are none savings only more mud. I am voting for the status quo, but if we're thinking changes lets think about what a possible outcome of stepping back to C if expression semantics would be. Being implicit it might even tackle at immortal if interface == nil gotcha.

PS. (Edit)

I think the boilerplate that counts is what people see.

But wasn't this given as a rationale for the try proposal? "On popular demand...".

(I remember your rebuke to mine (at go-newbie times) proposal of iirc watch some years ago.
**Spooky action at distance**. I was then wrong, you were right. Try will allow for the spooky action not only at distance but also well hidden in layers of wrapped calls.)

@networkimprov
Copy link
Author

networkimprov commented Jul 2, 2019

@ianlancetaylor there's nothing like try() (either its arguments or effects) in the language now either, so apparently it's ok to devise novel schemes to reduce error handling boilerplate :-)

What on err offers that try() lacks is application to virtually all errors, including recoverable ones.

Sure, the concept needs work to support a variety of use cases in a way that blends with the rest of Go.

@ianlancetaylor
Copy link
Contributor

I did not mean to imply that it was not OK to devise novel schemes. But any novel scheme is a cost, which must be balanced by some benefit. I was listing the benefits and costs I see of this scheme.

@networkimprov
Copy link
Author

So the issues I am most concerned about have already been addressed.

Perhaps so, but it would still benefit the debate for Go team members to state publicly whatever personal objections they have. I know a few hundred people who'd like to hear them :-)

I don't think you'e accommodated the objection that it removes very little boilerplate compared to if

We could define return err as the default action, tho I think it's better to be explicit:

err := f()
catch ; // semicolon to clarify that handler wasn't omitted by mistake?

@ianlancetaylor
Copy link
Contributor

If the members of the Go team have personal objections to the try proposal, they will state them.

@ianlancetaylor
Copy link
Contributor

As discussed above in several different comments, this proposal adds language complexity while removing little of the error handling boilerplate. Given that there is little enthusiasm for eliminating error handling boilerplate alone, the cost of this proposal is simply not worth the marginal benefit that it provides. This proposal is basically a different way of writing an if statement.

-- writing for @golang/proposal-review

@peterbourgon
Copy link

peterbourgon commented Jul 17, 2019

Forgive me if this is the wrong place for this feedback.

Given that there is little enthusiasm for eliminating error handling boilerplate alone

I guess this may be getting at what Robert wrote in the closing comment to the try proposal, when he noted that it’s worth clarifying what the problem we’re trying to solve actually is. But I challenge this summary: in my opinion it is precisely and only the boilerplate that should be reduced, the current semantics—error handling as separate statements/blocks directly adjacent to the error-generating code—are important and foundational and should not be changed.

edit: Some questions that we should probably come to a rough consensus on as a community, before spending too much time on future proposals, include, in my opinion:

  • Are we addressing only specific, common error scenarios, or trying to improve all of them?
  • Are we only reducing boilerplate, or are we also changing error handling semantics?
  • Are we explicitly allowing chaining of multiple error-generating expressions in one statement?
  • (Related) Are we proposing a new keyword, a new builtin, or something else? Why is this best?

@ianlancetaylor
Copy link
Contributor

@peterbourgon I don't think I agree with what you say but I doubt this is the best place for this discussion. In any case I don't think that discussion affects the decision on this issue. No matter what you consider to be boilerplate, this proposal doesn't remove enough of it to be worth the cost.

@peterbourgon
Copy link

peterbourgon commented Jul 17, 2019

I don’t agree that this proposal doesn’t remove enough boilerplate (edit in response to below: my rationale), but that is of course subjective. I would love to have a more productive conversation about this; if the time and place for that conversation becomes apparent, please keep me in the loop.

@ianlancetaylor
Copy link
Contributor

My analysis of the boilerplate for this specific proposal can be seen at #32611 (comment) .

@urban-wombat
Copy link

I think option 3. from networkimprov has been underestimated (to quote):
"go fmt could just allow single-line if statements:
Would it also allow single-line case, for, else, var () ? I'd like them all, please ;-)
The Go team has turned aside many requests for single-line error checks."

If gofmt were to allow single-statement if-statements for err != nil to appear on a single line,
visual clutter would be greatly reduced, without any change to the Go language.

I come from C and I greatly value the enormous effort and experience that has gone into making
Go simple to understand and use. I also appreciate gofmt which removes a lot of trivial formatting
decision making.

A 3-line boilerplate handball up the stack of an error from a called function is not the end of the
world for me or anyone else.

But ... in libraries I wrote in C, I returned an error value from almost every function and handballed it up the
stack with a single line if statement.

Where an error needed to be originated, or a returned error needed to be decorated, I did that on
multiple lines.

There was no error handling clutter, except where it counted: new error generation, and decoration.

Error generation, decoration and handballing was clear and uncluttered. And that was achieved by
the C equivalent of:

if err != nil { return err }

@ianlancetaylor
Copy link
Contributor

@urban-wombat You already opened #33113 for that. I suggest that if you want to mention it on other issues that you point to that one. That seems better than scattering discussion over a range of different issues. Thanks.

@ianlancetaylor
Copy link
Contributor

@peterbourgon I re-read your earlier rationale, and I still feel that this specific change doesn't make enough of a difference to make it worth changing the language. This is particularly true given the additional non-orthogonal concepts that this change implies: a new restricted form of expressions and a new statement syntax.

@thepudds
Copy link
Contributor

Hi @peterbourgon, some quick comments.

This current proposal is fairly broad, including the proposal itself has a fair number of permutations

Sometimes, "broader" is good because it means the solution is more general. In other cases, "broader" increases the cost of a change, or increases overlap with existing alternatives.

For example, as originally written, as far as I followed, any zero value is supported and it is not specific to error types, which would mean something like this is supported under the original proposal here:

someMap := getMap()
on someMap, goto label

In that case, that is a broader capability than strictly targeting error handling (which in turn requires a judgement call as to whether or not it is good to be broader in that way).

I think there is a narrower form of this proposal that is closer to what I think I might have seen you sketch roughly as:

a, err := f()
on err, errors.Annotatef(err, "message")

In any event, because this proposal has a fair number of permutations, in some cases it has been hard at least for me to understand which permutation a specific comment is addressing.

(Finally, I am speaking for myself as a more-or-less random gopher from the broader community. In other words, just to be explicit, I am not member of the core Go team).

@networkimprov
Copy link
Author

There is no need to continue this discussion.

If you read carefully, it's clear that the Go team was only interested in:
a) zero lines of boilerplate (this proposal specifies 1 line), and
b) no changes to assignment LHS (as in #32500).

IOW, only an expression like check or try() is acceptable to the Go team. That is clearly a non-starter with the community, so we are at an impasse. We should therefore let the Go team focus on other work, e.g. generics.

Thanks for everyone's interest,
Liam

@peterbourgon
Copy link

It’s clear that the Go team was only interested in:
a) zero lines of boilerplate (this proposal specifies 1 line), and
b) no changes to assignment LHS (as in #32500).

This should be confirmed by the Go team and not merely assumed. (This is also what I was driving at with my set of scoping questions here.)

@ianlancetaylor
Copy link
Contributor

Speaking purely personally, I don't agree with @networkimprov 's comment at #32611 (comment) . We don't have specific requirements of that sort. If we did, we would say them.

We do our best to judge each proposal on its own merits. Every language change has costs and benefits. I want to stress, again, that this specific proposal introduced new concepts into the language, and that is a cost. We need a benefit that is worth that cost, and in the opinion of the proposal review team the benefit of this specific proposal was not worth the cost of this specific proposal.

To conclude from that that the Go team is only interested in "zero lines of boilerplate" or "no changes to assignment LHS" is to misunderstand what we are saying. We are weighing costs and benefits. We are not saying that only certain kinds of changes are acceptable. We are saying that the benefits of a change must outweigh the costs of a change.

@networkimprov
Copy link
Author

I don't believe you're misleading us, but I stand by my assessment of the Go team's interests. You write as if "costs" and "benefits" were objective measurements. They are subjective in this case, and therefore influenced by preferences which one may not be fully aware of.

@ianlancetaylor
Copy link
Contributor

You're right of course that costs and benefits are somewhat subjective. As has been said, Go is an opinionated language.

That said I think that you are simply mistaken in the conclusions that you are drawing. I think it's clear that Go has considerable tolerance for boilerplate. I don't see how it's possible to conclude that the Go team would only accept a proposal that reduces boilerplate down to zero lines.

I agree that additional syntax on the left hand side of an assignment statement is a bigger lift, since there is nothing like that in Go today. Currently the left hand side of an assignment must be addressable, be a map index expression, or be the special identifier _. In those three cases assignment is a fairly straightforward operation: just copy or discard a value. To change the left hand side of an assignment statement to cause some action other than assignment to occur is a big cost and requires a big benefit.

But to say that any of these ideas are ruled out a priori is to misunderstand the nature of costs and benefits. Even if costs and benefits are subjective, which to some extent they are, it doesn't mean that the costs can not be incurred. It just means that they need a corresponding benefit.

Clearly there are some changes that will never be made to Go. For example, we will never break significant amounts of existing Go code; that is a cost that is so high that it's impossible to imagine a corresponding benefit. But I don't see why the kinds of changes we are talking about here rise to that level.

I could very well be wrong, but it seems to me that in these discussions you persistently underestimate the costs of a language change. That underestimation, if real, may lead you to conclude that the Go team must not see the benefits of the change. I encourage you to seriously consider that perhaps we do see at least some of the benefits, but that we judge the costs differently than you do.

Above you tried to separate cost to the spec, cost to the users, and cost to the tooling. That is very different from the way that I view the costs of language changes. The only cost that matters is the cost of understanding the language. Every special case in the language (and the language has far too many special cases already) is a cost. The goal is a language that is simple and orthogonal. Every concept in the language should ideally interact with every other concept clearly and simply. When certain kinds of expressions that can only be used in one place, when we add certain kinds of statement syntax that can only be used with one statement, those are big costs.

As you know, the discussion of the try proposal stressed that it was a builtin function that changed no other aspect of the language. That is another way of saying that cost of try was limited to understanding one more special case, a special case akin to the existing special cases of the builtin functions panic and recover. There was no new syntax, no new constraints, no affect on general language orthogonality. Those were important points in limiting the cost of try. Of course it still had costs, and the heated discussion was in itself a significant cost, and in the end we agreed that those costs were too high for the benefit of try.

@networkimprov
Copy link
Author

A belief that "the only cost that matters is the cost of understanding the language" may guide you into future community resistance, because we are all acutely aware of what I called the "cost to the users/ecoysystem" #32611 (comment).

I based my assessment above on statements by Robert and Russ in other discussions. And "zero new statements" is an equivalent criterion to "zero boilerplate lines". Re cost of a language change, the Go team was first to propose such a thing: check/handle.

That's all I'll say on the matter, and you'll be relieved to hear that I have no strong opinions on generics :-)

@ianlancetaylor
Copy link
Contributor

I continue to disagree that "cost to the users/ecosystem" is distinct from what I am considering.

I want to make clear that I did not say or imply anything like "zero new statements."

@urban-wombat
Copy link

Thanks ianlancetaylor, point taken.

See my further comment elaborating on proposal #33113 and especially the Go Playground code samples:-

  1. Current gofmt formatting
  2. Proposed gofmt formatting

@Michael-F-Ellis
Copy link

Michael-F-Ellis commented Jul 19, 2019 via email

@networkimprov
Copy link
Author

May I impose on the last two commenters to re-post their comments on #33113 and delete the copies above? Thanks.

@Michael-F-Ellis
Copy link

Michael-F-Ellis commented Jul 19, 2019 via email

@liam
Copy link

liam commented Jul 19, 2019 via email

@bradfitz bradfitz added the error-handling Language & library change proposals that are about error handling. label Oct 29, 2019
@golang golang locked and limited conversation to collaborators Oct 28, 2020
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
error-handling Language & library change proposals that are about error handling. FrozenDueToAge LanguageChange Suggested changes to the Go language Proposal v2 An incompatible library change
Projects
None yet
Development

No branches or pull requests