data-structures: github.com/timtadh/data-structures/exc Index | Files

package exc

import "github.com/timtadh/data-structures/exc"

Exceptions for Go as a Library

Explanation: http://hackthology.com/exceptions-for-go-as-a-library.html

Go (golang) lacks support for exceptions found in many other languages. There are good reasons for Go to not include exceptions. For instance, by making error handling explicit the programmer is forced to think concretely about the correct action to take. Fined grained control over the handling of errors using multiple return parameters is one of Go's strengths.

However, there are cases where Go programs do not universally benefit from the explicit handling of errors. For instance, consider the following code:

func DoStuff(a, b, c interface{}) error {
	x, err := foo(a)
	if err != nil {
		return err
	}
	y, err := bar(b, x)
	if err != nil {
		return err
	}
	z, err := bax(c, x, y)
	if err != nil {
		return err
	}
	return baz(x, y, z)
}

If Go had exceptions such code could be easily simplified:

func DoStuff(a, b, c interface{}) throws error {
	x := foo(a)
	y := bar(b, x)
	baz(x, y, bax(c, x, y)
}

This library allow you to write go with exceptions and try-catch-finally blocks. It is not appropriate for all situations but can simplify some application code. Libraries and external APIs should continue to conform to the Go standard of returning error values.

Here is an example of the `DoStuff` function where foo, bar and baz all throw exceptions instead of returning errors. (We will look at the case where they return errors that you want to turn into exceptions next). We want DoStuff to be an public API function and return an error:

func DoStuff(a, b, c interface{}) error {
	return exc.Try(func() {
		x := foo(a)
		y := bar(b, x)
		baz(x, y, bax(c, x, y)
	}).Error()
}

Now let's consider the case where we want to catch the exception log and reraise it:

func DoStuff(a, b, c interface{}) error {
	return exc.Try(func() {
		x := foo(a)
		y := bar(b, x)
		baz(x, y, bax(c, x, y)
	}).Catch(&exc.Exception{}, func(t exc.Throwable) {
		log.Log(t)
		exc.Rethrow(t, exc.Errorf("rethrow after logging"))
	}).Error()
}

Rethrow will chain the Throwable `t` with the new `*Error` created such that if/when the exception reaches the top level you know exactly how it was created and where it was rethrown.

Ok, what about interacting with regular Go APIs which return errors? How can we turn those errors into exceptions? The easy was is to use the `ThrowOnError` function which is a sugar for:

if err != nil {
	ThrowErr(ErrorFrom(err)
}

So converting out original `DoStuff` function we get

func DoStuff(a, b, c interface{}) { // Throws
	x, err := foo(a)
	exc.ThrowOnError(err)
	y, err := bar(b, x)
	exc.ThrowOnError(err)
	z, err := bax(c, x, y)
	exc.ThrowOnError(err)
	exc.ThrowOnError(baz(x, y, z))
}

This package also supports: catching user defined exceptions, catching multiple exception types, `Close` which works like the "try with resources" construct in Java 7+, (multiple) finally blocks, and a choice between propogating exceptions with `Unwind` or retrieving the error/exception with `Error` and `Exception` functions.

One Gotcha! The `Try()` function creates a `*Block` struct. To execute the block you must either call: `Unwind`, `Error`, or `Exception`. `Unwind` executes the block, if there is an exception coming out of the block it continues to cause the program stack unwind. `Error` and `Exception` excute the block, but return the exception as a value to deal with in the usual Go way.

Index

Package Files

block.go doc.go exception.go

func Rethrow Uses

func Rethrow(e Throwable, err *Error)

Rethrow an object. It chains on the *Error as the reason for the rethrow and where it occured. If you are inside of a catch block you should use this method instead of Throw*

exc.Try(func() {
	exc.Throwf("wat!@!")
}).Catch(&Exception{}, func(e Throwable) {
	t.Log("Caught", e)
	exc.Rethrow(e, Errorf("rethrow"))
}).Unwind()

func Throw Uses

func Throw(e Throwable)

Throw a Throwable object. This is how you throw a custom exception:

type MyException struct {
	exc.Exception
}

exc.Try(func() {
	exc.Throw(&MyException{*Errorf("My Exception").Exception()})
}).Catch(&Exception{}, func(t Throwable) {
	log.Log("caught!")
}).Unwind()

func ThrowErr Uses

func ThrowErr(e *Error)

Throw an *Exception created from an *Error.

func ThrowOnError Uses

func ThrowOnError(err error)

Throw an *Exception created from a regular error interface object. The stack trace for the exception will point to the throw rather than the creation of the error object.

func Throwf Uses

func Throwf(format string, args ...interface{})

Throw a new *Exception created from an *Error made with Errorf. Basically a drop in replacement for everywhere you used fmt.Errorf but would now like to throw an exception without creating a custom exception type.

type Block Uses

type Block struct {
    // contains filtered or unexported fields
}

Represents a Try/Catch/Finally block. Created by a Try or Close function. Executed by Unwind, Error, or Exception. You may add multiple Catch and Finally expressions to your *Block with the Catch and Finally functions. Catch and Finally can be in arbitrary orders unlike in languages with syntatic support for Exceptions. I recommend you put your Catch expressions before your finally Expressions. Finally and Catch expressions are evaluated in the order they are declared.

func Close Uses

func Close(makeCloser func() io.Closer, try func(io.Closer)) *Block

Start a Try/Catch/Finally block. You supply to functions, the first one creates a closable resource and returns it. This resources is supplied to the the second function which acts as a normal try. Whether the try fails or succeeds the Close() function is always called on the resource that was created (and returned) by the first function. Further, catch and finally functions may be chained onto the Close function. However, they will be run after the Close function is called on the resource and will not have access to it.

Finally, if the function to created the closable object fails, it will not be cleaned up if it was partially initialized. This is because it was never returned. There are also ways to deal with that situation using an outer Try/Finally.

func Try Uses

func Try(try func()) *Block

Start a Try/Catch/Finally block. Any exception thrown in the Try block can be caught by a Catch block (if registered for the Exception type or a parent exception type).

func (*Block) Catch Uses

func (b *Block) Catch(exc Throwable, do func(Throwable)) *Block

Add a catch function for a specific Throwable. If your Throwable struct "inherits" from another struct like so:

type MyException struct {
	exc.Exception
}

Then you can catch *MyException with *Exception. eg:

exc.Try(func() {
	Throw(&MyException{*Errorf("My Exception").Exception()})
}).Catch(&Exception{}, func(t Throwable) {
	log.Log("caught!")
}).Unwind()

Catch blocks are only run in the case of an thrown exception. Regular panics are ignored and will behave as normal.

func (*Block) Error Uses

func (b *Block) Error() error

Run the Try/Catch/Finally *Block. If there is an uncaught (or rethrown) exception continue return it as an error.

The Block will NOT BE RUN unless this method, Unwind, or Exception is called. This could lead to an difficult to track down bug!

func (*Block) Exception Uses

func (b *Block) Exception() Throwable

Run the Try/Catch/Finally *Block. If there is an uncaught (or rethrown) exception continue return it as a Throwable.

The Block will NOT BE RUN unless this method, Unwind, or Error is called. This could lead to an difficult to track down bug!

func (*Block) Finally Uses

func (b *Block) Finally(finally func()) *Block

Add a finally block. These will be run whether or not an exception was thrown. However, if a regular panic occurs this function will not be run and the panic will behave as normal.

func (*Block) Unwind Uses

func (b *Block) Unwind()

Run the Try/Catch/Finally *Block. If there is an uncaught (or rethrown) exception continue to propogate it up the stack (unwinding it). This would be the normal behavoir in language which natively support exceptions.

The Block will NOT BE RUN unless this method, Error, or Exception is called. This could lead to an difficult to track down bug!

type Error Uses

type Error struct {
    Err   error
    Stack []byte
}

The *Error struct wraps up a regular error (could be any error) and a stack trace. The idea is that it track where the error was created.

func Errorf Uses

func Errorf(format string, args ...interface{}) *Error

A drop in replacement for either fmt.Errorf or errors.Errorf. Creates and *Error with a stack trace from where Errorf() was called from.

func FromError Uses

func FromError(err error) *Error

Create an *Error from an existing error value. It will create a stack trace to attach to the error from where FromError() was called from.

func (*Error) Error Uses

func (e *Error) Error() string

Format the error and stack trace.

func (*Error) Exception Uses

func (e *Error) Exception() *Exception

Create an Exception object from the Error.

func (*Error) String Uses

func (e *Error) String() string

Format the error and stack trace.

type Exception Uses

type Exception struct {
    Errors []*Error
}

An implementation of Throwable you can base your custom Exception types off of. It is also the type of Throwable thrown by Throwf. To "inherit" from Exception use this formula:

type MyException struct {
	exc.Exception
}

This ensures that your new exception will be catchable when *Exception is supplied. See *Block.Catch for details.

func (*Exception) Chain Uses

func (e *Exception) Chain(err *Error) Throwable

Add another *Error to the list of *Error

func (*Exception) Error Uses

func (e *Exception) Error() string

Format an error string from the list of *Error.

func (*Exception) Exc Uses

func (e *Exception) Exc() *Exception

Return itself.

func (*Exception) Join Uses

func (e *Exception) Join(exc *Exception) *Exception

Join this exception with another exception.

func (*Exception) String Uses

func (e *Exception) String() string

Format an error string from the list of *Error.

type Throwable Uses

type Throwable interface {
    Exc() *Exception
    Error() string
    Chain(e *Error) Throwable
}

The interface that represents what can be thrown (and caught) by this library. All Throwables must be convertible to and *Exception, implement the error interface and allow chaining on of extra errors so that they can be Rethrown.

Package exc imports 5 packages (graph). Updated 2016-07-28. Refresh now. Tools for package owners.