Go errors : Is() and As() claim to be recursive, 是否有实现错误接口(interface)并支持此递归的任何类型 - 无错误?

标签 go pointers types error-handling error-checking

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

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

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

最终编辑:

感谢@mkopriva's answer和下面的评论,我现在有一个直接的方法来实现这个(尽管,我仍然很好奇是否有一些标准类型可以做到这一点)。由于没有示例,我创建示例的尝试失败了。我缺少的部分是向我的类型添加 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
}

var Err0 = errors.New("error 0")
var Err1 = errors.New("error 1")
var Err2 = errors.New("error 2")
var Err3 = errors.New("error 3")

func main() {
    //Check basic Is functionality
    errs := Wrap(Err1, Err2, Err3)
    fmt.Println(errs)                            //error 1: error 2: error 3
    fmt.Println(errors.Is(errs, Err0))           //false
    fmt.Println(errors.Is(errs, Err2))           //true
}

一边走 source特别提到了定义 Is 方法的能力,example没有以可以解决我的问题的方式实现它,并且讨论没有立即明确表明需要利用 errors.Is 的递归性质。

现在回到原来的帖子:

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

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

这给我带来了麻烦。我可以天真地让 errors.Iserrors.As 递归地处理自定义错误类型。这很简单。

  1. 创建一个实现错误接口(interface)的类型(有一个 Error() string 方法)
  2. 该类型必须有一个成员来表示包装错误,该成员是指向其自身类型的指针。
  3. 实现一个返回包装错误的 Unwrap() error 方法。
  4. 实现一些将一个错误与另一个错误包装起来的方法

看起来不错。但是有麻烦。

因为错误是指针,如果我做类似 myWrappedError = Wrap(Err1, Err2) 的事情(在这种情况下假设 Err1Err2< 包裹)。 errors.Is(myWrappedError, Err1)errors.Is(myWrappedError, Err2) 不仅会返回 true,errors.Is(Err2, Err1) 也会返回 true )

如果需要生成 myOtherWrappedError = Wrap(Err3, Err2) 并稍后调用 errors.Is(myWrappedError, Err1),它现在将返回 false!制作 myOtherWrappedError 更改 myWrappedError

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

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

注意:我更感兴趣的可能是已经存在的正确方法,而不是我的基本尝试的具体错误

编辑 3:正如其中一个答案所建议的,我的第一个代码中的问题显然是我修改了全局错误。我知道,但未能充分沟通。下面,我将包含其他不使用指针且不修改全局变量的损坏代码。

编辑 4:稍微修改以使其更有效,但它仍然有问题

参见 https://go.dev/play/p/bSytCysbujX

type errorGroup struct {
    err        error
    wrappedErr error
}

//...implemention Unwrap and Error excluded for brevity

func Wrap(inside error, outside error) error {
    return &errorGroup{outside, inside}
}

var Err1 = errorGroup{errors.New("error 1"), nil}
var Err2 = errorGroup{errors.New("error 2"), nil}
var Err3 = errorGroup{errors.New("error 3"), nil}

func main() {
    errs := Wrap(Err1, Err2)
    errs = Wrap(errs, Err3)
    fmt.Println(errs)//error 3: error 2: error 1
    fmt.Println(errors.Is(errs, Err1)) //true
    fmt.Println(errors.Is(errs, Err2)) //false <--- a bigger problem
    fmt.Println(errors.Is(errs, Err3)) //false <--- a bigger problem
}

编辑 2:缩短了 playground 版本

参见 https://go.dev/play/p/swFPajbMcXA举个例子。

编辑 1:我的代码的精简版侧重于重要部分:

type errorGroup struct {
    err        error
    wrappedErr *errorGroup
}

//...implemention Unwrap and Error excluded for brevity

func Wrap(errs ...*errorGroup) (r *errorGroup) {
    r = &errorGroup{}
    for _, err := range errs {
        err.wrappedErr = r
        r = err

    }
    return
}

var Err0 = &errorGroup{errors.New("error 0"), nil}
var Err1 = &errorGroup{errors.New("error 1"), nil}
var Err2 = &errorGroup{errors.New("error 2"), nil}
var Err3 = &errorGroup{errors.New("error 3"), nil}

func main() {
    errs := Wrap(Err1, Err2, Err3)//error 3: error 2: error 1
    fmt.Println(errors.Is(errs, Err1)) //true

    //Creating another wrapped error using the Err1, Err2, or Err3 breaks the previous wrap, errs.
    _ = Wrap(Err0, Err2, Err3)
    fmt.Println(errors.Is(errs, Err1)) //false <--- the problem
}

最佳答案

你可以这样使用:

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 errors : Is() and As() claim to be recursive, 是否有实现错误接口(interface)并支持此递归的任何类型 - 无错误?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/71651182/

相关文章:

c++ - 为什么在增加指针的整数值时得到一个随机数?

c - 指针作为一个数组的索引 - C

c++ - 如何在 C++ 中打印 char 指针的所有指针值?

python - Python/mypy 中 NamedTuple 和 TypedDict 的主要区别是什么

function - 结构中缺少函数体和字符串标记

go - App Script Go Quickstart修改401错误

go - "reflect: Field index out of range"更新时出现 panic

python : terminology 'class' VS 'type'

methods - 一个 Rust 类型之谜 : Convert a function to a method, 和调用函数的签名破坏了编译

go - 关于 channel 顺序