Golang中如何将error类型断言为自定义错误类型

Golang中如何将error类型断言为自定义错误类型 我一直在声明哨兵错误类型,如这个示例所示。然而,从任何error(例如errA)到ErrB的类型断言似乎总是成功,即使errAErrA的一个实例。如果我使用类型别名的话可以理解这种情况,但ErrA类型和ErrB类型肯定是不同的吧?

我可以通过这种方法来解决问题,但对于第一种情况的解释是什么?有没有比使用struct更好的方法?

谢谢

4 回复

是的,现在想想就很明显了。ErrB 确实是一个新的接口类型,但它是一个与 error 具有相同布局的 interface 类型。我同样可以像这样定义 ErrB,类型断言也能正常工作。

更多关于Golang中如何将error类型断言为自定义错误类型的实战系列教程也可以访问 https://www.itying.com/category-94-b0.html


在Go语言中,error是一个接口类型

https://golang.org/ref/spec#Errors

当你对接口类型进行类型断言时,它只是检查是否满足给定的接口。也就是说(在你给出的示例中),检查errA是否满足errB接口。errA的值不会改变,但结果表达式是errB类型,并具有errB的方法。在这种情况下,errAerrB都是error的别名,所以类型断言实际上没有改变任何东西。你只是成功地将errA类型的东西转换成了errB类型。

你的Println()语句打印的是两个具有相同值的东西,所以它们打印的内容是相同的。

go.dev

缩略图

Go编程语言规范 - Go编程语言

非常量值 x 在以下任一情况下可转换为类型 T

  • x赋值T
  • 忽略结构体标签(见下文),x 的类型和 T 具有相同底层类型
  • 忽略结构体标签(见下文),x 的类型和 T 是指针类型且不是定义类型,并且它们的指针…

这解释了第一个例子。但我无法解释基于组合的第二个例子。

在Go语言中,当使用错误值(而非错误类型)进行类型断言时,如果错误值实现了目标接口,断言就会成功。在你的第一个示例中,ErrAErrB 都实现了 error 接口,并且它们都是通过相同的底层字符串类型实现的。由于类型断言检查的是值是否满足接口,而不是具体的类型,因此即使 errAErrA 类型,它也能被断言为 ErrB,因为两者底层都是字符串,且 ErrB 的方法集是 error 接口的子集。

以下是示例代码说明问题:

package main

import "fmt"

type ErrA string

func (e ErrA) Error() string {
    return string(e)
}

type ErrB string

func (e ErrB) Error() string {
    return string(e)
}

func main() {
    var errA error = ErrA("error A")
    
    // 类型断言为 ErrB 会成功,因为 errA 的底层值满足 ErrB 的接口
    if errB, ok := errA.(ErrB); ok {
        fmt.Printf("Assertion to ErrB succeeded: %v\n", errB)
    } else {
        fmt.Println("Assertion to ErrB failed")
    }
}

输出会是:

Assertion to ErrB succeeded: error A

这是因为 errA 的底层值是一个字符串,而 ErrB 也只需要一个字符串来实现 Error() string 方法。类型断言在这里检查的是值的行为,而不是具体类型。

为了解决这个问题,推荐使用结构体来封装错误,这样可以包含额外的类型信息。例如:

package main

import "fmt"

type ErrA struct {
    msg string
}

func (e ErrA) Error() string {
    return e.msg
}

type ErrB struct {
    msg string
}

func (e ErrB) Error() string {
    return e.msg
}

func main() {
    var errA error = ErrA{msg: "error A"}
    
    // 类型断言为 ErrB 会失败,因为底层类型不同
    if errB, ok := errA.(ErrB); ok {
        fmt.Printf("Assertion to ErrB succeeded: %v\n", errB)
    } else {
        fmt.Println("Assertion to ErrB failed")
    }
}

输出:

Assertion to ErrB failed

使用结构体是更可靠的方法,因为它确保了类型的唯一性。如果不想使用结构体,可以考虑在错误类型中添加一个未导出的方法,以在类型断言时区分它们,但这通常比结构体更复杂。结构体方法是最直接和清晰的解决方案。

回到顶部