skip to content
Alvin Lucillo

Wrapping errors

/ 3 min read

💻 Tech

Wrapping errors in Go helps preserve the original error and attach context each time the error is returned up to the call stack. By the time the error is handled, the handler can refer to the original error. There are different ways to wrap errors, but here I listed:

  1. errors.Wrapf using errors package
  2. %w formatting verb used with fmt.Errorf function of the fmt package

With errors.Wrapf, you can view more details from the error with %+v, which reveals the stack trace. Without it, it uses the default value returne of the implemented Error() function of the interface. In the example, the default value is customized.

package main

import (
	"fmt"

	"github.com/pkg/errors"
)

type customError struct {
	code int
}

func (c *customError) Error() string {
	return fmt.Sprintf("error occurred, code: %v", c.code)
}

func main() {
	if err := someFunc1(); err != nil {
		fmt.Printf("Stack trace: \n %+v \n", err)
		fmt.Printf("No trace: \n %v", err)
	}

}

func someFunc1() error {
	if err := someFunc2(); err != nil {
		return errors.Wrapf(err, "error calling someFunc2")
	}

	return nil
}

func someFunc2() error {
	if err := someFunc3(); err != nil {
		return errors.Wrapf(err, "error calling someFunc3")
	}

	return nil
}

func someFunc3() error {
	return &customError{1}
}
Stack trace: 
 error occurred, code: 1
error calling someFunc3
main.someFunc2
	/tmp/sandbox372597777/prog.go:37
main.someFunc1
	/tmp/sandbox372597777/prog.go:28
main.main
	/tmp/sandbox372597777/prog.go:20
runtime.main
	/usr/local/go-faketime/src/runtime/proc.go:272
runtime.goexit
	/usr/local/go-faketime/src/runtime/asm_amd64.s:1700
error calling someFunc2
main.someFunc1
	/tmp/sandbox372597777/prog.go:29
main.main
	/tmp/sandbox372597777/prog.go:20
runtime.main
	/usr/local/go-faketime/src/runtime/proc.go:272
runtime.goexit
	/usr/local/go-faketime/src/runtime/asm_amd64.s:1700 
No trace: 
 error calling someFunc2: error calling someFunc3: error occurred, code: 1
Program exited.

errors package is an external package, and you have the option to just use the standard library way with formatting verb %w with fmt.Errorf when wrapping the error. It does the same with Wrapf except that %w doesn’t track stack trace at the point the error occurred. You can see at the output that %+v and %v have the same output.

package main

import (
	"fmt"
)

type customError struct {
	code int
}

func (c *customError) Error() string {
	return fmt.Sprintf("error occurred, code: %v", c.code)
}

func main() {
	if err := someFunc1(); err != nil {
		fmt.Printf("Stack trace: \n %+v \n", err)
		fmt.Printf("No trace: \n %v", err)
	}

}

func someFunc1() error {
	if err := someFunc2(); err != nil {
		return fmt.Errorf("error calling someFunc2: %w", err)
	}

	return nil
}

func someFunc2() error {
	if err := someFunc3(); err != nil {
		return fmt.Errorf("error calling someFunc3: %w", err)
	}

	return nil
}

func someFunc3() error {
	return &customError{1}
}
Stack trace: 
 error calling someFunc2: error calling someFunc3: error occurred, code: 1 
No trace: 
 error calling someFunc2: error calling someFunc3: error occurred, code: 1
Program exited.