Succinct error handling for Go inspired by Rust. eh can help you avoid the usual Go error
handling boileplate.
This library tries to provide similar functionality to Rust's Result enum and
the ? operator.
For example turn this:
func example(aFile string) []byte, error {
f, err := os.Open(aFile)
if err != nil {
return nil, err
}
buff := make([]byte, 5)
_, err := f.Read(b1)
if err != nil {
return nil, err
}
return buff, nil
}Into this:
func example(aFile string) (res eh.Result[[]byte]) {
defer eh.EscapeHatch(&res) // must have pointer to the named output Result
buff := make([]byte, 5)
file := eh.NewResult(os.Open(aFile)).Eh()
_ = eh.NewResult(file.Read(buff)).Eh()
return eh.Result[[]byte]{Ok: buff}
}When an error occurs (for example if the file does not exist) this is what happens:
{Ok:[] Err:open non-existent-file.md: no such file or directory}
For a successful operation the output is like this:
{Ok:[60 33 100 111 99 116 121 112] Err: <nil>}
Furthermore, it introduces a flattened style of error handling that leverages Go's defer mechanism, eliminating nested if statements. This approach allows for clearer code readability, following the principle of "success logic flows down, error handling flows up."
We could turn this:
var InMemoryErr = errors.New("inmemory record not found")
var DbNotFoundError = errors.New("db record not found")
var ApiNotFound = errors.New("api record not found")
func tryFindFromMemory() (string, error) {return "", InMemoryErr }
func tryFindFromDb() (string, error) {return "", DbNotFoundError }
func tryFindFromApi() (string, error) {return "", ApiNotFound }
func example() (string, error) {
val, err := tryFindFromMemory()
if err == nil {
return val // success value returns here
}
if errors.Is(err, InMemoryErr) {
val, err = tryFindFromDb()
if err == nil {
return val
}
}
if errors.Is(err, DbNotFoundError) {
val, err = tryFindFromApi()
if err == nil {
return val
}
}
if errors.Is(err, ApiNotFound) {
return "I'm default value for ApiNotFound"
}
return "I'm default value for other errors"
}into this
var InMemoryErr = errors.New("inmemory record not found")
var DbNotFoundError = errors.New("db record not found")
var ApiNotFound = errors.New("api record not found")
func tryFindFromMemory() (string, error) {return "", InMemoryErr }
func tryFindFromDb() (string, error) {return "", DbNotFoundError }
func tryFindFromApi() (string, error) {return "", ApiNotFound }
func example() (res eh.Result[string]) {
eh.Fallback(&res, "I'm default value for other errors")
eh.Fallback(&res, "I'm default value for ApiNotFound", ApiNotFound)
eh.CatchError(&res, func(error) {
return eh.NewResult(mayFailWithErr3()).Eh()
}, DbNotFoundError)
eh.CatchError(&res, func(error) {
return eh.NewResult(tryFindFromDb()).Eh()
}, InMemoryErr)
successVal := eh.NewResult(tryFindFromMemory()).Eh()
return eh.Result[string]{Ok: successVal}
}The library provides just a handful or public structs and functions:
Resultstructs to capture an outcome that can potentially result in an errorEhmethod (?was not possible) that will stop the current function from executing if there is an error and return aResultthat contains the error. If there is no error theResultis unwrapped and theOkvalue is returned. Just like Canadians add "eh" to the end of a sentence to make it a question, you can callEhon aResultto get a similar functionality to the question mark operator.EscapeHatchfunction that should be deferred and given access to the enclosing function namedResultargument so that the error can be recovered and the enclosed function can be interrupted early if an error occurs.CatchErrorfunction should be deferred to correctly handle errors from the enclosed function before they reach theEscapeHatch.Fallbackfunction, which also serves as a deferred function, handles errors by providing a fallback value.
A few simple steps to incorporate in your code:
- Return a named
Resultfrom your function defertheEscapeHatchfunction with pointer to the namedResultargument- Wrap functions that return
any, errorintoeh.NewResultto getResult - Call
Eh()on anyResultto stop the execution if there is an error and returnResult{Err: Error}from the enclosing function.