为了账号安全,请及时绑定邮箱和手机立即绑定

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

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

Go
POPMUISE 2022-11-23 20:31:54

在我看来,在 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 库?


查看完整描述

4 回答

?
繁星coding

TA贡献0条经验 获得超4个赞

你可以使用这样的东西:


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


查看完整回答
反对 回复 2022-11-23
?
潇潇雨雨

TA贡献1553条经验 获得超3个赞

您将“很快”(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.Iserrors.As

  • Is如果树中的任何错误匹配,则报告匹配。

  • 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.Iserrors.As

现有的组合错误通过提供IsAs方法来检查包含的错误,要求每个实现复制此逻辑,可能以不兼容的方式。
这最好在errors.Is和中处理errors.As,出于同样的原因,这些函数处理奇异展开。

此外,该提案为生态系统提供了一种通用方法来表示组合错误,允许第三方实现之间的互操作。


查看完整回答
反对 回复 2022-11-23
?
一只萌萌小番薯

TA贡献1525条经验 获得超7个赞

您的代码修改了包全局错误值,因此它本质上是错误的。这个缺陷与 Go 的错误处理机制无关。

根据您链接的文档,有两个错误处理助手:Is, 和AsIs让你递归地解包一个错误,寻找一个特定的错误,这必须是一个全局包才能有用。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


查看完整回答
反对 回复 2022-11-23
?
翻阅古今

TA贡献1484条经验 获得超5个赞

有几种方法,但有一件事你应该记住:如果你有多个错误,你可能需要将它作为一个错误片段来处理


例如,假设您需要检查所有错误是否相同,或者至少存在一个特定类型的错误,您可以使用下面的代码片段。


您可以扩展这个概念或使用一些现有的库来处理多重错误


type Errors []error


func (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

}


查看完整回答
反对 回复 2022-11-23

添加回答

举报

0/150
提交
取消
意见反馈 帮助中心 APP下载
官方微信