Skip to content

proposal: errors: add NewJoinedErrors #48831

@goofinator

Description

@goofinator

Description

Sometimes I miss the capabilities of the standard library to handle errors. Wrapping an error in context with fmt.Errorf("some context: %w",err) is a great feature. But there are situations where having multiple errors of the form

var errMyError1 = errors.New("my error 1")
var errMyError2 = errors.New("my error 2")

I need to pack them both into an error followed by the ability to detect this fact with

errors.Is(err, errMyError1)
errors.Is(err, errMyError2)

Example Implementation

At the moment, I'm using my own type to solve such problems:

type joinedErrors struct {
	errExt error
	errInt error
}

func (e joinedErrors) Error() string {
	return fmt.Sprintf("%s: %s", e.errExt, e.errInt)
}

func (e joinedErrors) Unwrap() error {
	return e.errInt
}

func (e joinedErrors) Is(target error) bool {
	return errors.Is(e.errExt, target)
}

func (e joinedErrors) As(target interface{}) bool {
	return errors.As(e.errExt, target)
}

Using the following function

func NewJoinedErrors(errExt error, errInt error) error {
	return joinedErrors{errExt: errExt, errInt: errInt}
}

i can get what i want.

Example Using

someErr1 := errors.New("my error 1")
someErr2 := errors.New("my error 2")
err:=fmt.Errorf("error occured: %w", someErr1)
	
// errNegative1 := fmt.Errorf("%w: %w", someErr2, err) - will cause the error
// using the standard library
errNegative1 := fmt.Errorf("%w: %s", someErr2, err)
fmt.Printf("%q, %v, %v\n",errNegative1, errors.Is(errNegative1,someErr1), errors.Is(errNegative1,someErr2))	
errNegative2 := fmt.Errorf("%s: %w", someErr2, err)
fmt.Printf("%q, %v, %v\n",errNegative2, errors.Is(errNegative2,someErr1), errors.Is(errNegative2,someErr2))

// using the NewJoinedErrors
errPositive := NewJoinedErrors(someErr2, err)
fmt.Printf("%q, %v, %v\n",errPositive, errors.Is(errPositive,someErr1), errors.Is(errPositive,someErr2))

You can try it here

Conclusion

I think a function like NewJoinedErrors might be useful for someone and should probably be included in the errors package.
The only drawback I see is some inconvenience in managing the text context. The context can be added for the overridden errors separately or later for the resulting error.

As a user, it would be more convenient for me to use fmt.Errorf("some context %w: and more context: %w", errExt, errInt) notation instead of the proposed construct. In this case, the errExt, errInt ratio under the hood is specified by the order of variables, which may not be quite obvious (meaning which error will be retrieved first during Unwrap).

Another option is to add a specifier for external error: fmt.Errorf("some context %w: and more context: %W", errInt, errExt), so the user knows that errExt will be extracted first, though I cannot immediately imagine a situation where this really matters.

I believe that the above changes will not spoil backward compatibility and will give error handling a more generic look.

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions