[ ] Has this idea, or one like it, been proposed before?
[X] Does this affect error handling?
[ ] Is this about generics?
[ ] Is this change backward compatible? Breaking the Go 1 compatibility guarantee is a large cost and requires a large benefit
Has this idea, or one like it, been proposed before?
This idea involves several ideas, including handler functions and guard clauses, but it goes about it quite differently.
Does this affect error handling?
Many previous error handling proposals had issues with return values other than errors and the semantics around how handler functions were called and scoped. This attempts to work around those problems by instead using a similar higher-order function approach to what range-over-func uses.
Is this about generics?
No.
Proposal
With the range-over-func, Go, for the first time, has functionality in which calling a function can directly cause another function to return. The compiler generates a function from the body of the loop, but that function has special semantics around returns which will cause the function containing the loop to return when a return is executed inside of the loop body.
Despite this, the functionality is not particularly useful for error handling, as it is not only unweildy, but it actually often pollutes the code more than the standard if err != nil does:
I propose adding a concept of guard functions which are invoked via an operator (?, perhaps, but I'm not so stuck on this. I also thought of ||.) after a function call. These functions would take as arguments the return types of the function as well as a special function created automatically by the compiler. This extra function argument would take as arguments the returns types of the function in which the guard clause was used and, when called, would cause the function to return with those arguments. The guard function would return the same types as those it was called with, minus the extra function.
That explanation is a bit obtuse, so here's an example:
func handle[R, V any](ret func(R, error), v V, err error) V {
if err != nil {
var r R
ret(r, err)
}
return v, err
}
func Example() (int, error) {
v1 := strconv.ParseInt(str1, 10, 0) ? handle
v2 := strconv.ParseInt(str2, 10, 0) ? handle
return int(v1 + v2), nil
}
In the example, the ret argument to handle() is a special function created by the compiler. When handle() calls it, it causes Example() to return with the values that it was given. The arguments after the first to handle() are the return values of strconv.ParseInt(). The entire f() ? handle expression returns the return value of handle() as long as ret is never called.
I believe that this approach solves a number of problems with previous proposals. For one thing, it fits cleanly with generics to allow for crafting custom handler functions that work across a range of types. It also isn't error-specific: Handler functions could be written with any custom conditions on calling ret that they want to.
The syntax is not something that I'm stuck on, as mentioned previously. I think it should be operator-based just to avoid potential problems with new keywords, but the primary idea here is the special ret function, not the syntax. An alternative syntax could even be to put the guard function before the function being called, i.e. v := handle?strconv.ParseInt(str, 10, 0) or something.
Language Spec Changes
The section on function calls would be changed to mention guard clauses and guard functions, and these would then also be given their own section explaining their semantics.
Informal Change
Guard functions are regular functions designed to be called via a guard clause which are passed, along with other things, a special function that can be used to cause the calling function to return.
Is this change backward compatible?
Yes.
Orthogonality: How does this change interact or overlap with existing features?
I think it fits well with generics and the existing error handling mechanisms.
Would this change make Go easier or harder to learn, and why?
Harder. People got surprisingly confused over how the new iterator functions work, although they seem to have gotten used to them pretty quickly, but I expect that the reaction to this would be pretty similar due simply to its higher-order function based design.
Cost Description
Slightly more complexity in the language's syntax.
Changes to Go ToolChain
Anything that parses Go code would be affected.
Performance Costs
Likely a very small compile-time penalty, but probably essentially no run-time penalty with proper optimization.
Go Programming Experience
Experienced
Other Languages Experience
Elixir, JavaScript, Ruby, Kotlin, Dart, Python, C
Related Idea
Has this idea, or one like it, been proposed before?
This idea involves several ideas, including handler functions and guard clauses, but it goes about it quite differently.
Does this affect error handling?
Many previous error handling proposals had issues with return values other than errors and the semantics around how handler functions were called and scoped. This attempts to work around those problems by instead using a similar higher-order function approach to what range-over-func uses.
Is this about generics?
No.
Proposal
With the range-over-func, Go, for the first time, has functionality in which calling a function can directly cause another function to return. The compiler generates a function from the body of the loop, but that function has special semantics around returns which will cause the function containing the loop to return when a return is executed inside of the loop body.
Despite this, the functionality is not particularly useful for error handling, as it is not only unweildy, but it actually often pollutes the code more than the standard
if err != nil
does:Proposal
I propose adding a concept of guard functions which are invoked via an operator (
?
, perhaps, but I'm not so stuck on this. I also thought of||
.) after a function call. These functions would take as arguments the return types of the function as well as a special function created automatically by the compiler. This extra function argument would take as arguments the returns types of the function in which the guard clause was used and, when called, would cause the function to return with those arguments. The guard function would return the same types as those it was called with, minus the extra function.That explanation is a bit obtuse, so here's an example:
In the example, the
ret
argument tohandle()
is a special function created by the compiler. Whenhandle()
calls it, it causesExample()
to return with the values that it was given. The arguments after the first tohandle()
are the return values ofstrconv.ParseInt()
. The entiref() ? handle
expression returns the return value ofhandle()
as long asret
is never called.I believe that this approach solves a number of problems with previous proposals. For one thing, it fits cleanly with generics to allow for crafting custom handler functions that work across a range of types. It also isn't error-specific: Handler functions could be written with any custom conditions on calling
ret
that they want to.The syntax is not something that I'm stuck on, as mentioned previously. I think it should be operator-based just to avoid potential problems with new keywords, but the primary idea here is the special
ret
function, not the syntax. An alternative syntax could even be to put the guard function before the function being called, i.e.v := handle?strconv.ParseInt(str, 10, 0)
or something.Language Spec Changes
The section on function calls would be changed to mention guard clauses and guard functions, and these would then also be given their own section explaining their semantics.
Informal Change
Guard functions are regular functions designed to be called via a guard clause which are passed, along with other things, a special function that can be used to cause the calling function to return.
Is this change backward compatible?
Yes.
Orthogonality: How does this change interact or overlap with existing features?
I think it fits well with generics and the existing error handling mechanisms.
Would this change make Go easier or harder to learn, and why?
Harder. People got surprisingly confused over how the new iterator functions work, although they seem to have gotten used to them pretty quickly, but I expect that the reaction to this would be pretty similar due simply to its higher-order function based design.
Cost Description
Slightly more complexity in the language's syntax.
Changes to Go ToolChain
Anything that parses Go code would be affected.
Performance Costs
Likely a very small compile-time penalty, but probably essentially no run-time penalty with proper optimization.
Prototype
No response