On Monday, June 5, 2023 at 4:17:17 AM UTC+12 Shulhan wrote:

Dear gophers, 

I have been reading several proposals about error handling couple of 
months ago and today a light bulb come upon me, and then I write as much 
as I can think. I am not sure if its good or bad idea or even possible 
to implement. 

In this post, I will try as concise as possible. 
The full and up to date proposal is available at 
https://kilabit.info/journal/2023/go2_error_handling/ . 

Any feedback are welcome so I can see if this can move forward. 
Thanks in advance. 

== Background 

This proposal is based on "go2draft Error Handling". 

My critics to "go2draft Error Handling" is the missing correlation 
between handle and check. 
If we see one of the first code in the design, 

---- 
... 
handle err { 
return fmt.Errorf("copy %s %s: %v", src, dst, err) 
} 

r := check os.Open(src) 
... 
---- 

There is no explicit link between check keyword and how it will trigger 
handle err later. 
It is also break the contract between the signature of os.Open, that 
return an error in the second parameter, and the code that call it. 

This proposal try to make the link between them clear and keep the code 
flow explicit and readable. 

The goals is not to reduce number of lines but to minimize repetitive 
error handling. 


== Proposal 

This proposal introduces two new keywords and one new syntax for 
statement. 

The two new keywords are “WHEN” and “HANDLE”. 

---- 
When = "when" NonZeroValueStmt HandleCallStmt . 
NonZeroValueStmt = ExpressionStmt 
; I am not quite sure how to express non-zero value 
; expression here, so I will describe it below. 

HandleCallStmt = "handle" ( HandleName | "{" SimpleStmt "}" ) . 
HandleName = identifier . 
---- 

The HandleCallStmt will be executed if only if the statement in 
NonZeroValueStmt returned non-zero value of its type. 
For example, given the following variable declarations, 

---- 
var ( 
err = errors.New(`error`) 
slice = make([]byte, 1) 
no1 = 1 

no2 int 
ok bool 
) 
---- 

The result of when evaluation are below, 

---- 
when err // true, non-zero value of type error. 
when len(slice) == 0 // true, non-zero value of type bool. 
when no1 // true, non-zero value of type int. 
when no2 // false, zero value of int. 
when ok // false, zero value of bool. 
---- 

The HandleCallStmt can jump to handle by passing handle name or provide 
simple statement directly. 
If its simple statement, there should be no variable shadowing happen 
inside them. 

Example of calling handle by name, 

---- 
... 
when err handle myErrorHandle 

:myErrorHandle: 
return err 
---- 

Example of calling handle using simple statement, 

---- 
... 
when err handle { return err } 
---- 

The new syntax for statement is to declare label for handle and its body, 

---- 
HandleStmt = ":" HandleName ":" [SimpleStmt] [ReturnStmt | HandleCallStmt] 
. 
---- 

Each of `HandleStmt` MUST be declared at the bottom of function block. 
An `HandleStmt` can call other `HandleStmt` as long as the handle is above 
the 
current handle and it is not itself. 
Any statements below `HandleCallStmt` MUST not be executed. 

Unlike goto, each `HandleStmt` is independent on each other, one 
`HandleStmt` 
end on itself, either by calling `return` or `handle`, or by other 
`HandleStmt` and does not fallthrough below it. 

Given the list of handle below, 

---- 
:handle1: 
S0 
S1 
:handle2: 
handle handle1 
S3 
---- 

A `handle1` cannot call `handle2` because its below it. 
A `handle2` cannot call `handle2`, because its the same handle. 
A `handle2` can call `handle1`. 
The `handle1` execution stop at statement `S1`, not fallthrough below it. 
The `handle2` execution stop at statement "`handle handle1`", any 
statements 
below it will not be executed. 


The following function show an example of using this proposed error 
handling. 
Note that the handlers are defined several times here for showing the 
possible cases on how it can be used, the actual handlers probably only two 
or 
three. 

---- 
func ImportToDatabase(db *sql.DB, file string) (error) { 
when len(file) == 0 handle invalidInput 

f, err := os.Open(file) 
when err handle fileOpen 
// Adding `== nil` is OPTIONAL, the WHEN operation check for NON zero 
// value of returned function or instance. 

data, err := parse(f) 
when err handle parseError 

err = f.Close() 
// Inline error handle. 
when err handle { return fmt.Errorf(`%s: %w`, file, err) } 

tx, err := db.Begin() 
when err handle databaseError 

// One can join the statement with when using ';'. 
err = doSomething(tx, data); when err handle databaseError 

err = tx.Commit() 
when err handle databaseCommitError 

var outofscope string 
_ = outofscope 

// The function body stop here if its not expecting RETURN, otherwise 
// explicit RETURN must be declared. 

return nil 

:invalidInput: 
// If the function expect RETURN, the compiler will reject and return 
// an error indicating missing return. 

:fileOpen: 
// All the instances of variables declared in function body until this 
// handler called is visible, similar to goto. 
return fmt.Errorf(`failed to open %s: %w`, file, err) 

:parseError: 
errClose := f.Close() 
when errClose handle { err = wrapError(err, errClose) } 

// The value of err instance in this scope become value returned by 
// wrapError, no shadowing on statement inside inline handle. 
return fmt.Errorf(`invalid file data: %s: %w`, file, err) 

:databaseError: 
_ = db.Rollback() 
// Accessing variable below the scope of handler will not compilable, 
// similar to goto. 
fmt.Println(outofscope) 
return fmt.Errorf(`database operation failed: %w`, err) 

:databaseCommitError: 
// A handle can call another handle as long as its above the current 
// handle. 
// Any statements below it will not be executed. 
handle databaseError 

RETURN nil // This statement will never be reached. 
} 
---- 

That's it. What do you guys think?



I don't really understand the comparison between this proposal and the 
referenced previous one. 
This new proposal effectively makes you have to handle errors for every 
call site, just like we do now, but with the indirect flow of jumping to a 
new section of code. And it requires 2 new keywords and new label syntax to 
achieve it. Could we not replicate this behavior the same way with nested 
local scope functions as the handlers and just call them with normal if 
logic? 

-- 
You received this message because you are subscribed to the Google Groups 
"golang-nuts" group.
To unsubscribe from this group and stop receiving emails from it, send an email 
to golang-nuts+unsubscr...@googlegroups.com.
To view this discussion on the web visit 
https://groups.google.com/d/msgid/golang-nuts/7e0e9d1a-d60b-4eca-b2c4-eef922a4f4aen%40googlegroups.com.

Reply via email to