Skip to content

Instantly share code, notes, and snippets.

@BenjamenMeyer
Last active June 28, 2022 16:29
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save BenjamenMeyer/ef9926913dcc3b165da8f25a459442a9 to your computer and use it in GitHub Desktop.
Save BenjamenMeyer/ef9926913dcc3b165da8f25a459442a9 to your computer and use it in GitHub Desktop.
golang error wrapping
package main
import (
"errors"
"fmt"
)
var (
Err1 error = errors.New("Err1")
Err2 error = errors.New("Err2")
Err3 error = errors.New("Err3")
Err4 error = errors.New("Err4")
)
func getSumErr() []error {
return []error{
Err1,
Err2,
Err3,
Err4,
}
}
func checkForErrors(theErr error, allErrors []error) {
fmt.Printf("Final error: %v\n", theErr)
for _, ae := range allErrors {
if errors.Is(theErr, ae) {
fmt.Printf("Found %v\n", ae)
} else {
fmt.Printf("Did NOT find %v\n", ae)
}
}
}
func method1() {
sumErrs := getSumErr()
genErr := fmt.Errorf("My final error. verr: %w", sumErrs)
checkForErrors(genErr, sumErrs)
}
func method2() {
sumErrs := getSumErr()
genErr := fmt.Errorf("My final error")
for _, se := range sumErrs {
genErr = fmt.Errorf("%v: %w", genErr, se)
}
checkForErrors(genErr, sumErrs)
}
func main() {
fmt.Println("Method 1")
method1()
fmt.Println("Method 2")
method2()
}
Method 1
Final error: My final error. verr: %!w([]error=[0xc000010250 0xc000010260 0xc000010270 0xc000010280])
Did NOT find Err1
Did NOT find Err2
Did NOT find Err3
Did NOT find Err4
Method 2
Final error: My final error: Err1: Err2: Err3: Err4
Did NOT find Err1
Did NOT find Err2
Did NOT find Err3
Found Err4
Program exited.
@BenjamenMeyer
Copy link
Author

NOTES:

  • The values of the getSumErr() as the errors built up throughout the code when an error is returned
  • The value of genErr as sent to checkForErrors() as the final error seen by the layer testing for an error

Unfortunately currently (Golang 1.18) can't detect that the earlier errors (Err1, Err2, Err3) are part of the error.

I even tried adding:

func backToErrArray(theErr error) (allErrors []error) {
	baseErr := theErr
	allErrors = []error{baseErr}
	for {
		wrappedErr := errors.Unwrap(baseErr)
		if wrappedErr == nil {
			return
		}
		fmt.Printf("Unwrapped: %v from %v", wrappedErr, baseErr)
		baseErr = wrappedErr
		allErrors = append(allErrors, baseErr)
	}
}

However, that pulls out Err4:

Unwrapped: Err4 from My final error: Err1: Err2: Err3: Err4Wrapped Errors: [My final error: Err1: Err2: Err3: Err4 Err4]

The other errors are not unwrapped. Perhaps proposals like golang/go#53435 can help solve that in future versions of Golang.

@BenjamenMeyer
Copy link
Author

NOTE: Consider that the code receiving genErr doesn't know anything about the code that's generated Err1, Err2, and Err3 but the code generating Err4 noted their existing in its documentation. This could be for multiple reasons - Err2 could be a File not opened type error with Err3 and Err4 providing more details. The handler may or may not be able to do something about Err2 but it has to be able to detect it first. This promotes error re-use:

var ErrSomeErr error = errors.New("some error")
...
return fmt.Errorf("%w: some detail", ErrSomeErr)
...
return fmt.Errorf("%w: some more details", wrappedErrSomeErr)
...
return fmt.Errorf("%w: some other detail", wrappedWrappedErrSomeErr)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment