英文:
Go - How to combine multiple error objects
问题
假设你想将strconv.Atoi返回的错误与你的命名错误ErrSomething结合起来,以便在不丢失底层错误信息的情况下,使用你的错误"常量"来检查出错的具体原因。你可以使用fmt.Errorf函数来实现这一点。以下是修改后的代码示例:
package lib
import (
"errors"
"fmt"
"strconv"
)
var ErrSomething = errors.New("foobar")
func SomeFunc(str string) (int, error) {
i, err := strconv.Atoi(str)
if err != nil {
return 0, fmt.Errorf("%w: %v", ErrSomething, err)
}
// 进行其他操作,可能返回其他错误
return i, nil
}
在这个示例中,我们使用fmt.Errorf函数来将ErrSomething和err结合起来。%w占位符用于将错误包装为一个Unwrap错误,以便在后续的错误处理中使用errors.Is函数进行检查。通过这种方式,你的用户可以使用errors.Is(err, ErrSomething)来检查错误是否是ErrSomething。
英文:
Say I have the following code:
package lib
import (
"errors"
"strconv"
)
var ErrSomething = errors.New("foobar")
func SomeFunc(str string) (int, error) {
i, err := strconv.Atoi(str)
if err != nil {
// how to combine ErrSomething with err?
return 0, fmt.Errorf("%w: %w", ErrSomething, err)
}
// do other things, potentially return other errors
return i
}
How do I combine the error returned from strconv.Atoi with my named error ErrSomething. The reason for combining is so that users of my SomeFunc() can check what exactly went wrong using my error "constants" while not losing information about the underlying error.
I have read similar questions but the usual answer is to just do: return 0, fmt.Errorf("foobar: %w", err) but this way my users can't check the error using errors.Is(err, ???)
答案1
得分: 4
你可以通过创建一个实现Is和Unwrap方法的错误类型来实现所需的行为,示例如下:
package lib
import (
"fmt"
"strconv"
)
type FoobarError struct {
msg string
original error
}
func (err *FoobarError) Error() string {
return fmt.Sprintf("%s: %s", err.msg, err.original.Error())
}
func (err *FoobarError) Unwrap() error {
return err.original
}
func (err *FoobarError) Is(target error) bool {
_, ok := target.(*FoobarError)
return ok
}
func SomeFunc() error {
// 这里使用strconv.ErrSyntax作为一个虚拟错误,用于表示可能由strconv.Atoi或其他操作返回的错误。
err := strconv.ErrSyntax
return &FoobarError{"foobar", err}
}
用法:
package main
import (
"errors"
"fmt"
"strconv"
"lib"
)
func main() {
if err := lib.SomeFunc(); err != nil {
fmt.Println(err) // foobar: invalid syntax
fmt.Println(errors.Is(err, &lib.FoobarError{})) // true
fmt.Println(errors.Is(err, strconv.ErrSyntax)) // true
}
}
你可以在这里了解更多关于这种方法的信息。
附加内容
类似于Go的os.IsExist,你可能有兴趣在你的库中添加一个辅助函数,以便用户更容易地检查错误:
package lib
import (
"errors"
// ...
)
// ...
func IsFoobar(err error) bool {
return errors.Is(err, &FoobarError{})
}
用法:
package main
// ...
func main() {
err := lib.SomeFunc();
if lib.IsFoobar(err) {
// ...
}
}
英文:
You can achieve the desired behavior by creating an error type that implements the Is and Unwrap methods as follows:
package lib
import (
"fmt"
"strconv"
)
type FoobarError struct {
msg string
original error
}
func (err *FoobarError) Error() string {
return fmt.Sprintf("%s: %s", err.msg, err.original.Error())
}
func (err *FoobarError) Unwrap() error {
return err.original
}
func (err *FoobarError) Is(target error) bool {
_, ok := target.(*FoobarError)
return ok
}
func SomeFunc() error {
// strconv.ErrSyntax is used as a dummy error here for the error
// that might be returned by strconv.Atoi or any other operation.
err := strconv.ErrSyntax
return &FoobarError{"foobar", err}
}
Usage:
package main
import (
"errors"
"fmt"
"strconv"
"lib"
)
func main() {
if err := lib.SomeFunc(); err != nil {
fmt.Println(err) // foobar: invalid syntax
fmt.Println(errors.Is(err, &lib.FoobarError{})) // true
fmt.Println(errors.Is(err, strconv.ErrSyntax)) // true
}
}
You can read more about this approach here.
Bonus
Similar to Go's os.IsExist, you may be interested in adding a helper function to your library that makes it easier for the user to check the error:
package lib
import (
"errors"
// ...
)
// ...
func IsFoobar(err error) bool {
return errors.Is(err, &FoobarError{})
}
Usage:
package main
// ...
func main() {
err := lib.SomeFunc();
if lib.IsFoobar(err) {
// ...
}
}
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。


评论