Thanks for the pointer Sven! I think I was once told not to use panic/defer/recover to implement generic exceptions in Go, and never reconsidered it. I think I'm taking up on your suggestion for my library.
On Wednesday, June 28, 2023 at 7:30:41 PM UTC+2 Sven Anderson wrote: > I think for what you want to do you don't need any language extension. You > can implement all that within the current language. Look at this example: > https://go.dev/play/p/aqjwzknrArH > > If you want to catch errors also in nested functions, you can also skip > the ErrorChecker type and do it like this: > https://go.dev/play/p/3Fk-82zxtUJ > > If you don't like the named return parameter, you can use a wrapper > function: > https://go.dev/play/p/C54vQThPUnq > > I thought about putting something like that in a package on github. But it > is such a small code, that I think copy and paste is just fine. :-) > > Cheers > > > On Sun, Jun 4, 2023 at 6:17 PM Shulhan <m.sh...@gmail.com> 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? >> >> -- >> 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...@googlegroups.com. >> > To view this discussion on the web visit >> https://groups.google.com/d/msgid/golang-nuts/20230604231646.78bf1fda%40inspiro.localdomain >> . >> > -- 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/a9885370-a81c-4598-8871-a5a3a831d329n%40googlegroups.com.