err3

package module
v0.0.0-...-b7055eb Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Sep 18, 2022 License: MIT Imports: 2 Imported by: 0

README

err3

The package provides tools for compact and composeable error handling. Instead of the traditional:

x, err := f()
if err != nil {
	return fmt.Sprintf("annotate %v", err)
}

You can write:

x := err3.Try1(f())(err3.Fmt("annotate"))

Fork

This is a fork of github.com/lainio/err2 with a different user facing API. Internally the panic/recovery mechanism of propagating errors is the same.

Tracing is handled differently. There is no automatic mechanism for printing panics, instead users should create their own standard way of doing this. Errors themselves are wrapped up with a stack trace that can be recovered and printed with "%+v". The original stack trace printing code is still available under the stackprint module.

Structure

err3 has the following package structure:

  • The top-level main package err3 can be imported as err3 which combines both the err3/err3 and err3/try packages
  • The err3/err3 package includes declarative error handling functions.
  • The err3/try package offers error checking functions.
  • The stackprint package contains the original code from err2 to help print stack traces
  • The assert package contains the original code from err2 to help with assertions.

Error checks

The functions CheckX and TryX are used for checking and handling errors. For example, instead of

b, err := ioutil.ReadAll(r)
if err != nil {
        return err
}
...

we can call

b := err3.Check1(ioutil.ReadAll(r))
...

But they do require a deferred error handler at the top of the function.

Error handling

Every function which uses err3 for error-checking should have at least one err3.Handle* function declared with defer. These functions recover the error. If this is ommitted, an error will panic up the stack until there is a recover.

This is the simplest form of err3.Handle*.

func do() error {
	defer err3.Handlef(&err, "do")
	...
}

There is also

  • Handlew: wrap the error with %w instead of %v
  • Handle: call a function with the error
  • HandleCleanup: call a cleanup function

There are also helpers CatchError, CatchAll, and ErrorFromRecovery that are useful for catching errors and panics in functions that do not return errors. These are generally callbacks, goroutines, and main.

Background

The original err2 implements similar error handling mechanism as drafted in the original check/handle proposal.

err3 encourages the single use of a defer statement at the top of the function and then using the err3.TryX functions to explicitly declare the error handlers for a function, similar to this new proposal.

The package accomplishes error handling internally by using panic/recovery, which is less than ideal. However, it works out well because:

  • benchmarks show that when there is no error the overhead is non-existant
  • it helps with properly handling panics

In general code should not pass errors in performance sensitive paths. Normally if it does (for example APIs that use EOF as an error), the API is not well designed.

The mandatory use of the defer might prevent some code optimisations like function inlining. If you have highly performance sensitive code it is best not to use this library, particularly in functions that are not benchmarked.

The following form introduces no overhead in all Go versions:

x, err := f()
err3.Check(Err)

This form introduces minimal overhead in go 1.19, but in other versions (including go 1.20 which you can verify by setting GOEXPERIMENT=unified) introduces no overhead. On go 1.19 it shows as taking an additional 1.7 nanoseconds, which is 6x slower than the original.

_ = err3.Check1(f())
Automatic And Optimized Stack Tracing

By default, TryX and CheckX will wrap the error so that it has a stack trace This can be disabled by setting the AddStackTrace = false

Documentation

Overview

Package err3 provides three main functionality:

  1. err3 package includes helper functions for error recovery and handling
  2. try package is for error checking and handling

The traditional error handling idiom in Go is roughly akin to

if err != nil { return err }

The err3 package drives programmers to focus more on error handling rather than checking errors. We think that checks should be so easy that we never forget them. The CopyFile example shows how it works:

// CopyFile copies source file to the given destination. If any error occurs it
// returns error value describing the reason.
func CopyFile(src, dst string) (err error) {
     // Add first error handler just to annotate the error properly.
     defer err3.Handlef(&err, "copy %s %s", src, dst)

     // Try to open the file. If error occurs now, err will be annotated and
     // returned properly thanks to above err3.Returnf.
     r := try.Check1(os.Open(src))
     defer r.Close()

     // Try to create a file. If error occurs now, err will be annotated and
     // returned properly.
     w := try.Try1(os.Create(dst))(try.Cleanup(func() {
     	os.Remove(dst)
     })
     defer w.Close()

     // Try to copy the file. If error occurs now, all previous error handlers
     // will be called in the reversed order. And final return error is
     // properly annotated in all the cases.
     _ = try.Check1(io.Copy(w, r))

     // All OK, just return nil.
     return nil
}

Error checks

The try package provides convenient helpers to check the errors. For example, instead of

b, err := ioutil.ReadAll(r)
if err != nil {
   return err
}

we can write

b := try.Check1(ioutil.ReadAll(r))

Note that try.ToX functions are as fast as if err != nil statements. Please see the try package documentation for more information about the error checks.

Stack Tracing

By default, TryX and CheckX will wrap the error so that it has a stack trace This can be disabled by setting the `AddStackTrace = false`

Error handling

The beginning of every function should contain an `err3.Handle*` to ensure that errors are caught. Otherwise errors will escape the function as a panic and you will be relying on calling functions to properly recover from panics.

Package try is a package for reducing error handling verbosity

Instead of 'x, err := f(); if err != nil { return handler(err) }' One writes: 'x := Try1(f(), handler)

If the error is not nil it is automatically thrown via panic. It is then caught by 'Handle'

  import (
	"github.com/gregwebs/err3"
	_ "github.com/gregwebs/try"
  )

  func do() (err error) {
    defer err3.Handlew(&err, "do")

    x := Try1(f())(Formatw("called f"))
  }

Package try is a package for try.TryX functions that implement the error checking. try.TryX functions check 'if err != nil' and if it throws the err to the error handlers, which are implemented by the err3 package.

All of the try package functions should be as fast as the simple 'if err != nil {' statement, thanks to the compiler inlining and optimization. Currently though there is an

Note that try.ToX function names end to a number (x) because:

"No variadic type parameters. There is no support for variadic type parameters,
which would permit writing a single generic function that takes different
numbers of both type parameters and regular parameters." - Go Generics

The leading number at the end of the To2 tells that To2 takes two different non-error arguments, and the third one must be an error value.

Currently only To, To1, To2, and To3 are implemented, but more could be added.

Index

Constants

This section is empty.

Variables

View Source
var AddStackTrace bool = true

Functions

func Check

func Check(err error)

Check is a helper function to immediately return error values without adding an if statement with a return. If an error occurs, it panics the error. You must use err3.Handle... at the top of your function to catch the error and return it instead of continuing the panic. the Try... functions an be used instead of Check... to add an error handler

By default, Check will wrap the error so that it has a stack trace This can be disabled by setting the var AddStackTrace = false

func Check1

func Check1[T any](v T, err error) T

Check1 is the same as Check but passes along one extra value

func Check2

func Check2[T, U any](v1 T, v2 U, err error) (T, U)

Check2 is the same as Check but passes along two extra values

func Check3

func Check3[T, U, V any](v1 T, v2 U, v3 V, err error) (T, U, V)

Check2 is the same as Check but passes along three extra values

func Cleanup

func Cleanup(handler func()) func(error) error

func Fmt

func Fmt(format string, args ...any) func(error) error

func Fmtw

func Fmtw(format string, args ...any) func(error) error

func Try

func Try[E error](errE E, handler func(E) error, handlers ...func(error) error)

Try is a helper function to return error values without adding a large if statement. It replaces the following code:

err := f()
if err != nil {
	return handler(err)
}

With this code:

try.Try(f(), handler)

If the error value nil, it is a noop If the error value is non-nil, the handler functions will be applied to the error Then the non-nil error will be given to panic. You must use err3.Handle... at the top of your function to recover the error and return it instead of letting the panic continue to unwind

By default, Try will wrap the error so that it has a stack trace This can be disabled by setting the var AddStackTrace = false

func Try1

func Try1[T any, E error](v T, err E) func(func(E) error, ...func(error) error) T

Try1 operates similar to 'Try' The 1 indicates that one non-error value will be passed through. Try takes handler functions directly as arguments Due to limitations of the Go language, Try1 cannot. Instead Try1 returns a function that handlers are applied to. It replaces the following code:

x, err := f()
if err != nil {
	return handler(err)
}

With this code:

x := try.Try1(f())(handler)

func Try2

func Try2[T, U any, E error](v1 T, v2 U, err E) func(func(E) error, ...func(error) error) (T, U)

Try2 is the same as Try1 but passes through 2 values

func Try3

func Try3[T, U, V any, E error](v1 T, v2 U, v3 V, err E) func(func(E) error, ...func(error) error) (T, U, V)

Try2 is the same as Try1 but passes through 3 values

Types

This section is empty.

Directories

Path Synopsis
Package assert includes runtime assertion helpers both for normal execution as well as a helper packager for Go's testing.
Package assert includes runtime assertion helpers both for normal execution as well as a helper packager for Go's testing.
Package try is a package for reducing error handling verbosity
Package try is a package for reducing error handling verbosity

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL