My own implementation of sentinel errors (https://dave.cheney.net/tag/errors)
Errorsare constantserrors.IssupportWrapmethod to wrap original error witherrors.UnwrapmethodString.Newsupport to add context arguments for error message, whileerrors.Isstill compares error itselfError.WithStacksupport to store stack trace at time method called
My recommendation is to design your packages to return all errors which are declared. So you have to declare package level error, and wrap original error into your sentinel error.
For example, if your package works with filesystem, consumer don't know anything about io.EOF error.
io.EOF should be wrapped into your custom error type EOF and returned to consumer, so he won't search 3rd party packages errors.
package mypkg
import (
"io"
"errors"
serr "github.com/bdandy/go-errors"
)
const (
ErrEOF = serr.String("EOF")
ErrUnknown = serr.String("unknown error")
)
func testfunc() error {
return io.EOF
}
// TestEOF returns ErrEOF or ErrUknown on some unexpected cases
// io.EOF is wrapped into ErrEOF so mypkg has all expected errors in one place,
// so it's more easy to use mypkg and handle errors
// (you don't have to know anything about `io` package and it's behaviour)
func TestEOF() error {
err := testfunc()
switch {
case errors.Is(io.EOF):
return ErrEOF.New().Wrap(err)
default:
return ErrUknown
}
}Comparison with errors.Errorf and pkg/errors
goos: linux
goarch: amd64
pkg: github.com/bdandy/go-errors
cpu: Intel(R) Core(TM) i7-1065G7 CPU @ 1.30GHz
BenchmarkWrap-8 5007164 270.9 ns/op 136 B/op 6 allocs/op
BenchmarkWrapWithStack-8 1232276 947.3 ns/op 392 B/op 7 allocs/op
BenchmarkErrorfWrap-8 4218820 284.9 ns/op 64 B/op 3 allocs/op
BenchmarkPkgErrorWrap-8 1376254 858.2 ns/op 368 B/op 6 allocs/op
BenchmarkPkgErrorWrapWithStack-8 781378 1593 ns/op 672 B/op 9 allocs/op
PASS