In the hope of elevating the discussion of late, I've made a little speech as one might do at a lunchtime discussion with students or colleagues. Maybe this will make sense to some kindred spirits here...
Thoughts about facilitating error handling in Go(), and the try() proposal in particular, have inspired a great deal of thought and sharing, with heated disapproval by some and, it seems, general approval by many. In a sense this is all premature--the proposal is a test and the good/bad judgement will come over time.
One aspect of the topic that seems to cause alarms is the fact it looks like and in some ways acts like error-related try() features in C, C++, C#, Java, JavaScript, and SQL. This invites presumption of harm through similarity. The goal here is to offer an entirely different way to think about the issue. In this alternate view, some aspects are more readily understood and extensions and refinements more inviting. So, from here out, it is not about errors.
In 1969, C. A. R. Hoare, Mr. Quicksort and the father of the ideas in Go's channels, was inspired to seek a logical, mathematical basis for computer programming. He, Nicholas Wirth, Ole-Johan Dahl, and Edsger Dijkstra were marching down this path and they all realized that guarded computation was important; that statements of provable truth are the basis of rigor.
In my Fig. 1 here, you can see the problem, the result of math.Sqrt() is valid or invalid depending on the status of x; x in the domain of non-negative real numbers means Sqrt is good and x out of that domain means the opposite.
As a starting point, Hoare and the others embraced AJT's call for assertions, such as is shown in Fig 2. as is typical now and then. Let's call it an assertion statement. (Hoare actually proposed a keyword version, "assert <boolean expression>;" that would return with prejudice if the bool was false.)
We all know this. But, are other kinds of assertions interesting, beyond statements? Here are two others, the assertion expression and the assertion assignment, along with Alan Turing's original 1949 appeal for assertions:
What I've shown here is not something I'm aware of being implemented before, but both Fig. 3 and 4 show consistent specializations of assertion statement to substatements; in the one case, to a function's arguments and in the other, to the values being assigned. If we had these inline assertions, we could call functions with range checking clear, local, and concise, and assign return values with stated conditions known to have been proven. To quote Turing, "...from which the correctness of the whole program easily follows."
There is a subtle shift here, from asserting that x>=0 which is specific, to insisting that Sqrt() has not complained about a domain error, which is specific in meaning but omits specifying what that range might be. (AssertDomain(math.Log(x)) would have the same meaning but a different domain.)
Perhaps Figure 4 looks familiar to you. Compare to Fig. 5...
...as you can see, we've come to the great debate, but along a different path. If you can pretend that "try" did not have a decade of bitter meanings from other languages and common misuse, then you can see try as nothing more than an assertion assignment. In this view, disjoint from the error type, we can also see that other "same meaning different detail" uses could make sense, just like the Domain assertion of Fig 4. If I have test for error value != nil, then why not test for non-error pointer value == nil as in Fig. 6. Not only does this seem useful and thematically consistent, it happens to already be in Go!
Where you ask? Where has this nil-pointer-try been hiding? Right here:
So now we reach my destination: Go's "must" is an assertion at its heart. That your program is executing means "it must have or else we'd not be here." It is an extreme, fatal must. Assertions considered previously range from the fatal-must of the assertion statement to the return-with-error of the assertion assignment.
Should "try" be "must"? Should musts take an optional error argument that defines what is returned? Is there a beautiful way to unify compile-time must, init-time must, and runtime must? I'll not comment as my goal here is not to propose, rather to encourage each of us to offer insights that convey the wisdom of those who came before us as well as learnings from our own experience.
Michael
--