try

package module
v0.0.0-...-82f87c0 Latest Latest
Warning

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

Go to latest
Published: Aug 9, 2026 License: BSD-3-Clause Imports: 4 Imported by: 0

README

Try: Simplified Error Handling in Go

Fork from dsnet/try with some modifications.

func readfile() (err error) {
	defer try.Handle(&err)
	try.E1(os.ReadFile("notexist1"))
	try.E1(os.ReadFile("notexist2"))
	return nil
}
type CustomError struct{}
func (e *CustomError) TryRecover() error { return fmt.Errorf("custom error") }

func fail() { panic(CustomError{}) }

func tryRecover() (err error) {
	defer try.Handle(&err)
	fail()
	return nil
}

func main() {
	err := tryRecover()
	fmt.Println(err) // prints "custom error"
}

GoDev Build Status

This module reduces the syntactic cost of error handling in Go.

Example usage in a main program:

func main() {
    defer try.F(log.Fatal)
    b := try.E1(os.ReadFile(...))
    var v any
    try.E(json.Unmarshal(b, &v))
    ...
}

Example usage in a unit test:

func Test(t *testing.T) {
    defer try.F(t.Fatal)
    db := try.E1(setdb.Open(...))
    defer db.Close()
    ...
    try.E(db.Commit())
}

Code before try:

func (a *MixedArray) UnmarshalNext(uo json.UnmarshalOptions, d *json.Decoder) error {
    switch t, err := d.ReadToken(); {
    case err != nil:
        return err
    case t.Kind() != '[':
        return fmt.Errorf("got %v, expecting array start", t.Kind())
    }

    if err := uo.UnmarshalNext(d, &a.Scalar); err != nil {
        return err
    }
    if err := uo.UnmarshalNext(d, &a.Slice); err != nil {
        return err
    }
    if err := uo.UnmarshalNext(d, &a.Map); err != nil {
        return err
    }

    switch t, err := d.ReadToken(); {
    case err != nil:
        return err
    case t.Kind() != ']':
        return fmt.Errorf("got %v, expecting array end", t.Kind())
    }
    return nil
}

Code after try:

func (a *MixedArray) UnmarshalNext(uo json.UnmarshalOptions, d *json.Decoder) (err error) {
    defer try.Handle(&err)
    if t := try.E1(d.ReadToken()); t.Kind() != '[' {
        return fmt.Errorf("found %v, expecting array start", t.Kind())
    }
    try.E(uo.UnmarshalNext(d, &a.Scalar))
    try.E(uo.UnmarshalNext(d, &a.Slice))
    try.E(uo.UnmarshalNext(d, &a.Map))
    if t := try.E1(d.ReadToken()); t.Kind() != ']' {
        return fmt.Errorf("found %v, expecting array end", t.Kind())
    }
    return nil
}

See the documentation for more information.

Install

go get -u codeberg.org/yaklib/try

Semgrep rules

These semgrep rules can help prevent bugs and abuse:

rules:
  - id: non-deferred-try-handle
    patterns:
      - pattern-either:
          - pattern: try.F(...)
          - pattern: try.Handle(...)
          - pattern: try.Recover(...)
      - pattern-not: defer try.F(...)
      - pattern-not: defer try.Handle(...)
      - pattern-not: defer try.Recover(...)
    message: Calls to try handlers must be deferred
    severity: ERROR
    languages:
      - go
  - id: missing-try-handler
    patterns:
      - pattern-either:
          - pattern: try.E(...)
          - pattern: try.E1(...)
          - pattern: try.E2(...)
          - pattern: try.E3(...)
          - pattern: try.E4(...)
      - pattern-not-inside: |
          ...
          defer try.F(...)
          ...
      - pattern-not-inside: |
          ...
          defer try.Handle(...)
          ...
      - pattern-not-inside: |
          ...
          defer try.Recover(...)
          ...
    message: Calls to try.E[n] must have a matching function-local handler
    severity: ERROR
    languages:
      - go

License

BSD - See LICENSE file

Documentation

Overview

Package try emulates aspects of the ill-fated "try" proposal using generics. See https://golang.org/issue/32437 for inspiration.

Example usage:

func Fizz(...) (..., err error) {
	defer try.Handle(&err, func() {
		if err == io.EOF {
			err = io.ErrUnexpectedEOF
		}
	})
	... := try.E2(Buzz(...))
	return ..., nil
}

This package is a sharp tool and should be used with care. Quick and easy error handling can occlude critical error handling logic. Panic handling generally should not cross package boundaries or be an explicit part of an API.

Package try is a good fit for short Go programs and unit tests where development speed is a greater priority than reliability. Since the E functions panic if an error is encountered, recovering in such programs is optional.

Code before try:

func (a *MixedArray) UnmarshalNext(uo json.UnmarshalOptions, d *json.Decoder) error {
	switch t, err := d.ReadToken(); {
	case err != nil:
		return err
	case t.Kind() != '[':
		return fmt.Errorf("got %v, expecting array start", t.Kind())
	}

	if err := uo.UnmarshalNext(d, &a.Scalar); err != nil {
		return err
	}
	if err := uo.UnmarshalNext(d, &a.Slice); err != nil {
		return err
	}
	if err := uo.UnmarshalNext(d, &a.Map); err != nil {
		return err
	}

	switch t, err := d.ReadToken(); {
	case err != nil:
		return err
	case t.Kind() != ']':
		return fmt.Errorf("got %v, expecting array end", t.Kind())
	}
	return nil
}

Code after try:

func (a *MixedArray) UnmarshalNext(uo json.UnmarshalOptions, d *json.Decoder) (err error) {
	defer try.Handle(&err)
	if t := try.E1(d.ReadToken()); t.Kind() != '[' {
		return fmt.Errorf("found %v, expecting array start", t.Kind())
	}
	try.E(uo.UnmarshalNext(d, &a.Scalar))
	try.E(uo.UnmarshalNext(d, &a.Slice))
	try.E(uo.UnmarshalNext(d, &a.Map))
	if t := try.E1(d.ReadToken()); t.Kind() != ']' {
		return fmt.Errorf("found %v, expecting array end", t.Kind())
	}
	return nil
}

Quick tour of the API

The E family of functions all remove a final error return, panicking if non-nil.

Or and Or2 are non-panicking alternatives: they capture a call's results, and Else returns them if the error is nil; otherwise Else logs the error with slog.Error and returns the given fallback values instead.

port := try.Or(strconv.Atoi(s)).Else(8080)

Handle recovers from that panic and allows assignment of the error to a return error value. Each recovered error is also logged with slog.Error. Other panics are not recovered.

func f() (err error) {
	defer try.Handle(&err)
	...
}

Handle also accepts optional functions that are called after the error is assigned, which may inspect or modify the error.

func f() (err error) {
	defer try.Handle(&err, func() {
		if err == io.EOF {
			err = io.ErrUnexpectedEOF
		}
	})
	...
}

func foo(i int) (err error) {
	defer try.Handle(&err, func() {
		err = fmt.Errorf("unable to foo %d: %w", i, err)
	})
	...
}

F wraps an error with file and line information and calls a function on error. It inter-operates well with testing.TB and log.Fatal.

func TestFoo(t *testing.T) {
	defer try.F(t.Fatal)
	...
}

func main() {
	defer try.F(log.Fatal)
	...
}

Recover is like F, but it supports more complicated error handling by passing the error and runtime frame directly to a function.

func f() {
	defer try.Recover(func(err error, frame runtime.Frame) {
		// do something useful with err and frame
	})
	...
}

The recover functions (Handle, F, and Recover) only recover panic values raised by the E functions. As a special case, they also recover any panic value that implements

interface{ TryRecover() error }

where TryRecover reports a non-nil error. If only the pointer type of the panic value implements TryRecover, a pointer to a copy of the value is used, so such values may be panicked by value or by pointer. This permits packages that use their own panic-based error signaling to interoperate with this package without depending on it. Such panic values carry no frame information: Recover reports a zero runtime.Frame for them.

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

func E

func E(err error)

E panics if err is non-nil.

func E1

func E1[A any](a A, err error) A

E1 returns a as is. It panics if err is non-nil.

func E2

func E2[A, B any](a A, b B, err error) (A, B)

E2 returns a and b as is. It panics if err is non-nil.

func E3

func E3[A, B, C any](a A, b B, c C, err error) (A, B, C)

E3 returns a, b, and c as is. It panics if err is non-nil.

func E4

func E4[A, B, C, D any](a A, b B, c C, d D, err error) (A, B, C, D)

E4 returns a, b, c, and d as is. It panics if err is non-nil.

func F

func F(fn func(...any))

F recovers an error previously panicked with an E function, wraps it, and passes it to fn. The wrapping includes the file and line of the runtime frame in which it occurred. F pairs well with testing.TB.Fatal and log.Fatal.

func Handle

func Handle(errptr *error, fns ...func())

Handle recovers an error previously panicked with an E function and stores it into errptr. If it recovers an error, it logs it with slog.Error as "try: recovered: <error>", where the log record includes a "src" attribute of the form "<package>/<file>:<line>" if the frame in which the error occurred is known, and then calls each fn in order, which may inspect or modify the error through errptr.

func Or

func Or[A any](a A, err error) or1[A]

Or captures the result of a call with one non-error value for use with Else:

port := try.Or(strconv.Atoi(s)).Else(8080)

func Or2

func Or2[A, B any](a A, b B, err error) or2[A, B]

Or2 captures the result of a call with two non-error values for use with Else:

host, port := try.Or2(splitHostPort(addr)).Else("localhost", "80")

func Recover

func Recover(fn func(err error, frame runtime.Frame))

Recover recovers an error previously panicked with an E function. If it recovers an error, it calls fn with the error and the runtime frame in which it occurred.

Types

This section is empty.

Directories

Path Synopsis
This program demonstrates recovering panic values from a foreign package that implements TryRecover, alongside ordinary use of the E functions.
This program demonstrates recovering panic values from a foreign package that implements TryRecover, alongside ordinary use of the E functions.
extlib
Package extlib simulates a third-party library that uses its own panic-based error signaling.
Package extlib simulates a third-party library that uses its own panic-based error signaling.
or command
This program demonstrates the non-panicking Or functions, which return a fallback value when the call fails.
This program demonstrates the non-panicking Or functions, which return a fallback value when the call fails.
panic command
simple command
simple2 command

Jump to

Keyboard shortcuts

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