检查自定义错误的类型。

huangapple go评论92阅读模式
英文:

Go checking for the type of a custom Error

问题

我在使用Go语言中的自定义错误类型方面遇到了困难。
我阅读了这篇关于错误的博文

所以我尝试了以下代码:

在我的model.go文件中,我定义了一个自定义错误类型:

type ModelMissingError struct {
    msg string // 错误描述
}

func (e *ModelMissingError) Error() string { return e.msg }

在我的某个方法中,我像这样抛出了一个自定义错误:

...
return Model{}, &ModelMissingError{"no model found for id"}
...

在调用该方法的地方,我想要检查返回的错误类型,并在确实是ModelMissingError类型时采取相应的操作。

我该如何做到这一点?

我尝试了以下代码:

if err == model.ModelMissingError

结果是*type model.ModelMissingError is not an expression*

显然我漏掉了一些东西。

英文:

I am having a hard time using a custom Error type in Go.
I read this Blog post on Errors

So I tried this:

In my model.go I defined a custom error:

type ModelMissingError struct {
	msg string // description of error
}

func (e *ModelMissingError) Error() string { return e.msg }

In one of my methods I throw a custom error like this:

...
return Model{}, &ModelMissingError{"no model found for id"}
...

In the caller of that method I would like to check the error returned for its type and take action if it is in fact a ModelMissingError.

How can I do this?

I tried this:

if err == model.ModelMissingError 

The result is *type model.ModelMissingError is not an expression*

Clearly I am missing something.

答案1

得分: 122

阅读博客文章进一步揭示了一些Go语言的内容,如下所示:

serr, ok := err.(*model.ModelMissingError)

这是逗号ok惯用法,显然我需要重新学习一下Go语言之旅 <- 更新后的链接

---- 引用自Go语言之旅 ----

类型断言提供对接口值的底层具体值的访问。
t := i.(T)
该语句断言接口值i持有具体类型T,并将底层的T值赋给变量t。
如果i不持有T,则该语句将引发恐慌。
为了测试接口值是否持有特定类型,类型断言可以返回两个值:底层值和一个布尔值,报告断言是否成功。

英文:

Reading the Blog post further exposes a bit of Go like this:

serr, ok := err.(*model.ModelMissingError)

This is the comma ok idiom, clearly I need to re do my go lang tour <- UPDATED LINK

---- quoted from golang tour ----

A type assertion provides access to an interface value's underlying concrete value.
t := i.(T)
This statement asserts that the interface value i holds the concrete type T and assigns the underlying T value to the variable t.
If i does not hold a T, the statement will trigger a panic.
To test whether an interface value holds a specific type, a type assertion can return two values: the underlying value and a boolean value that reports whether the assertion succeeded.

答案2

得分: 51

我已经成功使用switch语句进行错误断言,代码如下:

 err := FuncModelMissingError()

 switch t := err.(type) {
 default:
     fmt.Println("不是模型缺失错误")
 case *ModelMissingError:
     fmt.Println("模型缺失错误", t)
}

希望这对你有帮助。

英文:

I have manged to make an error assertion using the switch statement as follows:

 err := FuncModelMissingError()

 switch t := err.(type) {
 default:
     fmt.Println(&quot;not a model missing error&quot;)
 case *ModelMissingError:
     fmt.Println(&quot;ModelMissingError&quot;, t)
}

I hope this helps you out.

答案3

得分: 47

要检查错误的类型,可以使用errors.As

> As会在错误链中找到与目标匹配的第一个错误[...] 错误与目标匹配的条件是,错误的具体值可以赋值给目标指向的值。

当然,类型一致性是可赋值性的一个条件。

代码示例如下:

target := &model.ModelMissingError{} 
if errors.As(err, &target) {
    fmt.Println(target) // 找不到对应的模型
}

请注意上面示例中对&的两次使用。这是因为:

> 如果目标不是一个非nil指针,指向实现了error接口的类型或任何接口类型的指针,As会引发panic。

在你的情况下,你在指针接收器上声明了Error() string方法,因此满足As的“指向实现了error接口的类型的指针”的要求是**ModelMissingError。所以你需要两次引用。

<hr>

另一个方法errors.Is用于检查的相等性。

> 如果一个错误等于目标或者它实现了一个Is(error) bool方法,使得Is(target)返回true,则认为错误与目标匹配。

这在处理固定错误值时非常有用,例如使用varconst声明的错误,比如标准库中的io.EOF。示例如下:

var ErrModelMissing = errors.New("no model found for id")

func foo() {
    err := bar()
    if errors.Is(err, ErrModelMissing) {
        fmt.Println(err) // 找不到对应的模型
    }
}

<hr>

请注意,Go 1.13中errors.Aserrors.Is的有用之处在于错误的解包。如果你检查任意长的调用堆栈顶部的错误,你必须记住,原始错误可能会在向上冒泡时被包装到其他错误中。因此,直接检查相等性或类型可赋值性是不够的。

err1 := fmt.Errorf("wrapped: %w", &ModelMissingError{})
target := &ModelMissingError{}
fmt.Println(errors.As(err1, &target)) // true

err2 := fmt.Errorf("wrapped: %w", FixedError)
fmt.Println(errors.Is(err2, FixedError)) // true
fmt.Println(err2 == FixedError) // false

此外,github.com/pkg/errors包与errors.Aserrors.Is兼容:

// import pkgerr "github.com/pkg/errors"

err3 := pkgerr.Wrap(pkgerr.Wrap(&ModelMissingError{}, "wrapped 1"), "wrapped 2")
fmt.Println(errors.As(err3, &target)) // true

Playground: https://play.golang.org/p/FEzggdBLCqq

<hr>

当然,如果你确定错误没有被包装,那么旧式的类型断言同样适用:

if myerr, ok := err.(*model.ModelMissingError); ok {
    // 处理myerr
}
英文:

To check the TYPE of the error, use errors.As

> As finds the first error in err's chain that matches target [...] ​An error matches target if the error's concrete value is assignable to the value pointed to by target

Of course type identity is a condition for assignability.

So it would look like:

target := &amp;model.ModelMissingError{} 
if errors.As(err, &amp;target) {
    fmt.Println(target) // no model found for id
}

Pay attention to the two uses of &amp; in the example above. This is because:
> As panics if target is not a non-nil pointer to either a type that implements error, or to any interface type.

In your case, you declared Error() string method on the pointer receiver, therefore "a pointer to the type that implements the error interface" to satisfy As is **ModelMissingError. So you need to address twice.

<hr>

The other method errors.Is checks for value equality.

> An error is considered to match a target if it is equal to that target or if it implements a method Is(error) bool such that Is(target) returns true.

This is useful for example in case of fixed error values, e.g. errors declared as var or const like the standard library io.EOF. As an example:

var ErrModelMissing = errors.New(&quot;no model found for id&quot;)

func foo() {
    err := bar()
    if errors.Is(err, ErrModelMissing) {
        fmt.Println(err) // no model found for id
    }
}

<hr>

Consider that the usefulness of Go 1.13 errors.As and errors.Is lies in error unwrapping. If you inspect the error at the top of an arbitrarily long call stack, you must remember that the original error may become wrapped into other errors while being bubbled up. Then directly checking for equality or type assignability is not enough.

	err1 := fmt.Errorf(&quot;wrapped: %w&quot;, &amp;ModelMissingError{})
	target := &amp;ModelMissingError{}
	fmt.Println(errors.As(err1, &amp;target)) // true
	
	err2 := fmt.Errorf(&quot;wrapped: %w&quot;, FixedError)
	fmt.Println(errors.Is(err2, FixedError)) // true
	fmt.Println(err2 == FixedError) // false

Additionally, the package github.com/pkg/errors is compatible with errors.As and errors.Is:

// import pkgerr &quot;github.com/pkg/errors&quot;

err3 := pkgerr.Wrap(pkgerr.Wrap(&amp;ModelMissingError{}, &quot;wrapped 1&quot;), &quot;wrapped 2&quot;)
fmt.Println(errors.As(err3, &amp;target)) // true

Playground: https://play.golang.org/p/FEzggdBLCqq

<hr>

Naturally, if you know for sure that the error is not wrapped, a good old type assertion works just as fine:

if myerr, ok := err.(*model.ModelMissingError); ok {
    // handle myerr
}

答案4

得分: 31

现在使用 Go 1.13,我们可以使用 errors 包中的以下内容:

if errors.Is(err, model.ModelMissingError) {...}

请参阅博客文章:https://blog.golang.org/go1.13-errors

英文:

Now with Go 1.13 we can use the following from the errors package:

if errors.Is(err, model.ModelMissingError) {...}

See the blog post: https://blog.golang.org/go1.13-errors

答案5

得分: 7

如果你正在跨类型进行验证:

switch err := errorFromFunction(); err.(type) {
    case nil:
        fmt.Println("没有错误,函数执行成功")
    case *ErrorType1:
        fmt.Println("Type1 错误:", err)
    case *ErrorType2:
        fmt.Println("Type2 错误:", err)
}

如果你知道错误类型并想要验证:

err, ok := err.(*ErrorType1)
英文:

If you are Validating across types:

switch err := errorFromFunction(); err.(type) {
	case nil:
		fmt.Println(&quot;No Error Function Executed Successfully&quot;)
	case *ErrorType1:
		fmt.Println(&quot;Type1 Error: &quot;, err)
	case *ErrorType2:
		fmt.Println(&quot;Type2 Error&quot;, err)
}

If you know the error and want to validate:

err, ok := err.(*ErrorType1)

答案6

得分: 2

如果您不知道错误类型是什么,可以使用reflect包。

fmt.Println(reflect.ValueOf(err).Type())

或者获取完整的错误链:

for err != nil {
  fmt.Println(reflect.ValueOf(err).Type())
  err = errors.Unwrap(err)
}
英文:

If you don't know what the error type is, you can use the reflect package

fmt.Println(reflect.ValueOf(err).Type())

or to get the full error chain

for err != nil {
  fmt.Println(reflect.ValueOf(err).Type())
  err = errors.Unwrap(err)
}

答案7

得分: 2

简要说明

  • Sentinel错误 - 使用errors.Is进行匹配
  • 结构化错误 - 使用errors.As进行匹配

详细说明

  • Sentinel错误

    • 全局错误变量
    var ErrExist = errors.New("文件已存在")
    var ErrPermission = errors.New("权限被拒绝")
    
    • 使用errors.Is进行匹配
    if errors.Is(err, ErrNotExist) {
      createFile(..)
    }
    
  • 结构化错误

    • 自定义错误类型
    type CustomError struct {
      Msg          string
    }
    
    func (e *CustomError) Error() string
    
    • 使用errors.As进行匹配
    var cusErr *CustomError
    if errors.As(err, &cusErr) {
      fmt.Print(cusErr.Msg)
    }
    
英文:

For Short

  • Sentinel errors - match with errors.Is
  • Structured errors - match with errors.As

Details

  • Sentinel errors

    • Global error variables
    var ErrExist = errors.New(&quot;file already exists&quot;)
    var ErrPermission = errors.New(&quot;permission denied&quot;)
    
    • match with errors.Is
    if errors.Is(err, ErrNotExist) {
      createFile(..)
    }
    
  • Structured errors

    • Custom error types
    type CustomError struct {
      Msg          string
    }
    
    func (e *CustomError) Error() string
    
    • Match with errors.As
    var cusErr *CustomError
    if errors.As(err, &amp;cusErr) {
      fmt.Print(cusErr.Msg)
    }
    

huangapple
  • 本文由 发表于 2014年5月22日 09:44:18
  • 转载请务必保留本文链接:https://go.coder-hub.com/23796543.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定