Go 错误:Is() 和 As() 声称是递归的,是否有任何类型实现了错误接口并支持这种递归

在我看来,在 Go 中“包装”错误的“方式”是使用带有 %w 动词的 fmt.Errof

https://go.dev/blog/go1.13-errors

但是,fmt.Errorf 不会递归地包装错误。无法使用它来包装三个先前定义的错误(Err1、Err2 和 Err3),然后使用 Is() 检查结果并为这三个错误中的每一个都获取 true。

最后编辑:

多亏了@mkopriva 的回答和下面的评论,我现在有了一种直接的方法来实现它(尽管,我仍然很好奇是否有某种标准类型可以做到这一点)。由于没有示例,我创建示例的尝试失败了。我缺少的部分是在我的类型中添加一个IsAs方法。因为自定义类型需要包含错误和指向下一个错误的指针,所以自定义IsAs方法允许我们比较自定义类型中包含的错误,而不是自定义类型本身。

这是一个工作示例:https ://go.dev/play/p/6BYGgIb728k

以上链接的亮点

type errorChain struct {

    err  error

    next *errorChain

}


//These two functions were the missing ingredient

//Defined this way allows for full functionality even if

//The wrapped errors are also chains or other custom types


func (c errorChain) Is(err error) bool { return errors.Is(c.err, err) }


func (c errorChain) As(target any) bool { return errors.As(c.err, target) }


//Omitting Error and Unwrap methods for brevity


func Wrap(errs ...error) error {

    out := errorChain{err: errs[0]}


    n := &out

    for _, err := range errs[1:] {

        n.next = &errorChain{err: err}

        n = n.next

    }

    return out

}

}

虽然 Go源代码特别提到了定义Is方法的能力,但该示例并未以可以解决我的问题的方式实现它,并且讨论并未立即明确表明需要利用errors.Is.

现在回到原来的帖子:

Go 中是否有内置的东西可以正常工作?

我尝试自己制作一个(几次尝试),但遇到了不良问题。这些问题源于这样一个事实,即 Go 中的错误似乎是通过地址进行比较的。即,如果 Err1 和 Err2 指向同一事物,则它们是相同的。

这给我带来了问题。我可以天真地获取errors.Iserrors.As使用自定义错误类型递归工作。这很简单。

  1. 创建一个实现错误接口的类型(有一个Error() string方法)

  2. 该类型必须有一个代表包装错误的成员,该成员是指向其自身类型的指针。

  3. 实现一个Unwrap() error返回包装错误的方法。

  4. 实现一些将一个错误与另一个错误包装起来的方法

好像不错 但是有麻烦。

由于错误是指针,如果我做类似的事情myWrappedError = Wrap(Err1, Err2)(在这种情况下假设Err1被包装Err2)。不仅 willerrors.Is(myWrappedError, Err1)errors.Is(myWrappedError, Err2)return true,而且 will 也会errors.Is(Err2, Err1)

如果需要 makemyOtherWrappedError = Wrap(Err3, Err2)和稍后调用errors.Is(myWrappedError, Err1)它现在将返回 false!进行myOtherWrappedError更改myWrappedError

我尝试了几种方法,但总是遇到相关问题。

这可能吗?是否有执行此操作的 Go 库?


POPMUISE
浏览 136回答 4
4回答

繁星coding

你可以使用这样的东西:type errorChain struct {    err  error    next *errorChain}func Wrap(errs ...error) error {    out := errorChain{err: errs[0]}    n := &out    for _, err := range errs[1:] {        n.next = &errorChain{err: err}        n = n.next    }    return out}func (c errorChain) Is(err error) bool {    return c.err == err}func (c errorChain) Unwrap() error {    if c.next != nil {        return c.next    }    return nil}https://go.dev/play/p/6oUGefSxhvF

潇潇雨雨

您将“很快”(Go 1.20?1.21?)能够返回错误的切片/树,而不是链接/包装。(同时,mdobak/go-xerrors是个不错的选择)看建议::errors添加对包装多个错误的支持背景从 Go 1.13 开始,错误可以通过提供Unwrap返回包装错误的方法来包装另一个错误。和函数对包装错误链errors.Is进行errors.As操作。一个常见的请求是寻求一种将错误列表组合成单个错误的方法。提议如果错误的类型具有方法,则错误会包装多个错误Unwrap() []error重用名称Unwrap避免了与现有单数Unwrap方法的歧义。从中返回一个长度为 0 的列表Unwrap意味着错误不会包含任何内容。调用方不得修改 返回的列表Unwrap。返回的列表Unwrap不得包含任何nil错误。我们将术语“ error chain”替换为“ error tree”。和函数已更新以解包多个错误errors.Is。errors.AsIs如果树中的任何错误匹配,则报告匹配。As在树的中序先序遍历中找到第一个匹配错误。该errors.Join函数提供了一个简单的实现multierr。它不会压平错误。// Join returns an error that wraps the given errors. // Any nil error values are discarded. // The error formats as the text of the given errors, separated by newlines. // Join returns nil if errs contains no non-nil values. func Join(errs ...error) error该函数允许格式化动词fmt.Errorf的多个实例。%w该errors.Unwrap函数不受影响:它在方法nil出错时返回Unwrap() []error。为什么这应该在标准库中?该提案添加了标准库之外无法提供的内容:直接支持 和 中的错误errors.Is树errors.As。现有的组合错误通过提供Is和As方法来检查包含的错误,要求每个实现复制此逻辑,可能以不兼容的方式。这最好在errors.Is和中处理errors.As,出于同样的原因,这些函数处理奇异展开。此外,该提案为生态系统提供了一种通用方法来表示组合错误,允许第三方实现之间的互操作。

一只萌萌小番薯

您的代码修改了包全局错误值,因此它本质上是错误的。这个缺陷与 Go 的错误处理机制无关。根据您链接的文档,有两个错误处理助手:Is, 和As。Is让你递归地解包一个错误,寻找一个特定的错误值,这必须是一个全局包才能有用。As,另一方面,允许您递归地解包错误以查找给定类型的任何已包装错误值。包装是如何工作的?您将错误 A 包装在一个新的错误值B中。Wrap()帮助程序必须返回一个新值,就像fmt.Errorf链接文档中的示例一样。Wrap助手不应该修改被包装的错误的值。该值应被视为不可变的。事实上,在任何正常的实现中,该值都是 type error,这样您就可以包装任何错误,而不是仅仅将自定义错误类型的同心值相互包装起来;并且,在这种情况下,您无权访问包装错误的字段来修改它们。本质上,Wrap大致应该是:func Wrap(err error) error {     return &errGroup{err} }就是这样。这不是很有用,因为您的实现errGroup实际上并没有做任何事情——它不提供有关发生的错误的详细信息,它只是其他错误的容器。为了让它有价值,它应该有一条string错误消息,或者像其他一些错误类型的方法IsNotFound,或者比仅仅使用errorand更有用的东西fmt.Errorf。根据您的示例代码中的用法,您似乎还假设用例是说“我想将 A 包装在 C 中的 B”中,这是我在野外从未见过的,我想不出任何需要的场景。包装的目的是说“我收到了错误 A,我将把它包装在错误 B 中以添加上下文,然后返回它”。调用者可能将该错误包装在错误 C 中,依此类推,这就是递归包装的价值所在。例如:https ://go.dev/play/p/XeoONx19dgX

翻阅古今

有几种方法,但有一件事你应该记住:如果你有多个错误,你可能需要将它作为一个错误片段来处理例如,假设您需要检查所有错误是否相同,或者至少存在一个特定类型的错误,您可以使用下面的代码片段。您可以扩展这个概念或使用一些现有的库来处理多重错误type Errors []errorfunc (errs Errors) String() string {  …}func (errs Errors) Any(target error) bool{    for _, err := range errs {        if errors.Is(err,target) {            return true        }    }    return false}func (errs Errors) All(target error) bool{    if len(errs) == 0 { return false }        for _, err := range errs {        if !errors.Is(err,target) {            return false        }    }    return true}
打开App,查看更多内容
随时随地看视频慕课网APP

相关分类

Go