我有一个错误值,将它打印到控制台上会显示Token is expired
我该如何将其与特定的错误值进行比较?我尝试过这样,但不起作用:
if err == errors.New("Token is expired") {
log.Printf("Unauthorised: %s\n", err)
}
我有一个错误值,将它打印到控制台上会显示Token is expired
我该如何将其与特定的错误值进行比较?我尝试过这样,但不起作用:
if err == errors.New("Token is expired") {
log.Printf("Unauthorised: %s\n", err)
}
err == myPkg.ErrTokenExpired
)不再是Go 1.13(2019年第三季度)的最佳实践方法。
发行说明中提到:e
可以通过提供返回w
的Unwrap
方法来包装另一个错误w
。程序可使用e
和w
,允许e
为w
提供额外的上下文或重新解释它,同时仍然允许程序基于w
做出决策。为了支持包装,fmt.Errorf
现在有一个%w
动词用于创建包装错误,并在errors
包中增加了三个新函数(errors.Unwrap
, errors.Is
和 errors.As
),简化了解包装错误和检查包装错误。
因此,错误值FAQ解释道:
You need to be prepared that errors you get may be wrapped.
If you currently compare errors using
==
, useerrors.Is
instead.
Example:
if err == io.ErrUnexpectedEOF
becomes
if errors.Is(err, io.ErrUnexpectedEOF)
- Checks of the form if
err != nil
need not be changed.- Comparisons to
io.EOF
need not be changed, becauseio.EOF
should never be wrapped.If you check for an error type using a type assertion or type switch, use
errors.As
instead. Example:if e, ok := err.(*os.PathError); ok
becomes
var e *os.PathError if errors.As(err, &e)
Also use this pattern to check whether an error implements an interface. (This is one of those rare cases when a pointer to an interface is appropriate.)
Rewrite a type switch as a sequence of
if-elses
.
这个答案适用于Go 1.12及之前的版本。
在库中定义一个错误值
package fruits
var NoMorePumpkins = errors.New("No more pumpkins")
在代码的任何地方都不要使用errors.New
创建错误,而应在发生错误时返回预定义的值,然后可以执行以下操作:
package shop
if err == fruits.NoMorePumpkins {
...
}
参考 io
包的错误。
通过添加方法来隐藏检查实现并使客户端代码对 fruits
包中的更改更加免疫,以改进此功能。
package fruits
func IsNoMorePumpkins(err error) bool {
return err == NoMorePumpkins
}
请参考os
包中的错误信息。
NoMorePumpkins
变量的值。如果能用 const
来实现就好了。 - captncraigfruits.NoMorePumpkins
,而是他们可能包装它,导致 ==
失败。 - cbednarski尝试
err.Error() == "Token is expired"
或者通过实现错误接口来创建自己的错误。
习惯上,包会导出它们使用的错误变量,以便他人可以与它们进行比较。
例如,如果来自名为 myPkg 的包的一个错误被定义为:
var ErrTokenExpired error = errors.New("Token is expired")
您可以直接将错误进行比较:
if err == myPkg.ErrTokenExpired {
log.Printf("Unauthorised: %s\n", err)
}
如果错误来自第三方包并且该包没有使用导出的错误变量,那么您可以简单地从 err.Error() 获取字符串并进行比较,但是要小心使用此方法,因为更改错误字符串可能不会在主要版本中发布,并会破坏您的业务逻辑。type error interface {
Error() string
}
最常用的错误实现是errors包中未导出的errorString类型:
// errorString is a trivial implementation of error.
type errorString struct {
s string
}
func (e *errorString) Error() string {
return e.s
}
package main
import (
"errors"
"fmt"
"io"
)
func main() {
err1 := fmt.Errorf("Error")
err2 := errors.New("Error")
err3 := io.EOF
fmt.Println(err1) //Error
fmt.Printf("%#v\n", err1) // &errors.errorString{s:"Error"}
fmt.Printf("%#v\n", err2) // &errors.errorString{s:"Error"}
fmt.Printf("%#v\n", err3) // &errors.errorString{s:"EOF"}
}
输出:
Error
&errors.errorString{s:"Error"}
&errors.errorString{s:"Error"}
&errors.errorString{s:"EOF"}
另请参见:比较运算符
比较运算符用于比较两个操作数并生成一个未命名的布尔值。在任何比较中,第一个操作数必须可以赋值给第二个操作数的类型,反之亦然。
等号运算符
==
和!=
适用于可比较的操作数。指针值是可比较的。如果两个指针值指向同一变量或都具有值为 nil,则它们相等。指向不同零大小变量的指针可能相等,也可能不相等。
接口值是可比较的。如果两个接口值具有相同的动态类型和相等的动态值,或者都具有值为 nil,则它们相等。
对于非接口类型 X 的值 x 和接口类型 T 的值 t,当 X 的值是可比较的并且 X 实现了 T 时,它们是可比较的。如果 t 的动态类型与 X 相同,并且 t 的动态值等于 x,则它们相等。
如果所有字段都是可比较的,则结构值是可比较的。如果它们相应的非空白字段相等,则两个结构值相等。
所以:
1- 您可以使用 Error()
,像这个可工作的代码一样 (Go Playground):
package main
import (
"errors"
"fmt"
)
func main() {
err1 := errors.New("Token is expired")
err2 := errors.New("Token is expired")
if err1.Error() == err2.Error() {
fmt.Println(err1.Error() == err2.Error()) // true
}
}
输出:
true
2- 你也可以将其与nil
进行比较,像这个可行的代码一样(Go Playground):
package main
import (
"errors"
"fmt"
)
func main() {
err1 := errors.New("Token is expired")
err2 := errors.New("Token is expired")
if err1 != nil {
fmt.Println(err1 == err2) // false
}
}
输出:
false
3- 此外,您可以将其与完全相同的错误进行比较,就像这个工作代码一样
(Go Playground):
package main
import (
"fmt"
"io"
)
func main() {
err1 := io.EOF
if err1 == io.EOF {
fmt.Println("err1 is : ", err1)
}
}
输出:
err1 is : EOF
不鼓励通过字符串比较错误。相反,应该按值比较错误。
package main
import "errors"
var NotFound = errors.New("not found")
func main() {
if err := doSomething(); errors.Is(err, NotFound) {
println(err)
}
}
func doSomething() error {
return NotFound
}
如果你是一个库的作者并且想要导出错误以便用户可以根据不同类型的错误采取不同的行动,那么这将非常有用。标准库也实现了这一点。
但是这种方法的问题在于,由于Go不支持不可变值,导出的值可能会被任何人更改。不过,你仍然可以使用字符串作为错误并将其设置为const
。
package main
type CustomError string
func (ce CustomError) Error() string {
return string(ce)
}
const NotFound CustomError = "not found"
func main() {
if err := doSomething(); errors.Is(err, NotFound) {
println(err)
}
}
func doSomething() error {
return NotFound
}
这种方法更冗长一些,但更安全。
你应该首先考虑按值比较错误,就像其他解决方案中所描述的那样:
if errors.Is(err1, err2) {
// do sth
}
然而,在某些情况下,从函数返回的错误信息可能有点复杂,例如,错误被多次包装,并且在每个函数调用中添加了上下文,例如 fmt.Errorf("一些上下文:%w", err)
,而您可能只想比较两个错误的错误信息。在这种情况下,您可以执行以下操作:
// SameErrorMessage checks whether two errors have the same messages.
func SameErrorMessage(err, target error) bool {
if target == nil || err == nil {
return err == target
}
return err.Error() == target.Error()
}
func main() {
...
if SameErrorMessage(err1, err2) {
// do sth
}
}
if err1.Error() == err2.Error() {
// do sth
}
err1
或 err2
为 nil
,那么您可能会遇到空指针解引用的运行时错误。errors.Is
只适用于常量错误。如果错误是动态创建的,例如包含上下文信息,新的错误实例无法与之进行比较。在这种情况下,必须使用 error.As
。我想发布一个案例,其中errors.Is
可以很好地处理具有非可比值
的自定义错误。
type CustomError struct {
Meta map[string]interface{}
Message string
}
func (c CustomError) Error() string {
return c.Message
}
var (
ErrorA = CustomError{Message: "msg", Meta: map[string]interface{}{"key": "value"}}
)
func DoSomething() error {
return ErrorA
}
func main() {
err := DoSomething()
if errors.Is(err, ErrorA) {
fmt.Println("error is errorA")
} else {
fmt.Println("error is NOT errorA")
}
}
输出
error is NOT errorA
根本原因
原因是errors.Is
检查target
是否可比较。
func Is(err, target error) bool {
if target == nil {
return err == target
}
isComparable := reflectlite.TypeOf(target).Comparable()
Go语言中的comparable
类型有:
布尔型、数字型、字符串型、指针型、通道型、可比较类型的数组、所有字段都是可比较类型的结构体
由于CustomError
的Meta map[string]interface{}
不是可比较的,所以errors.Is
检查失败了。
解决方法
ErrorA = &CustomError{Message: "msg", Meta: map[string]interface{}{"key": "value"}}
声明为指针。CustomError.Is(target error)
来比较这个自定义类型。参考@kaizenCoder的评论。func (c CustomError) Is(err error) bool {
cErr, ok := err.(CustomError)
if !ok {
return false
}
if c.Message != cErr.Message || fmt.Sprint(c.Meta) != fmt.Sprint(c.Meta) {
return false
}
return true
}
err := DoSomething()
if ErrorA.Is(err) {
fmt.Println("error is errorA")
} else {
fmt.Println("error is NOT errorA")
}
CustomError.Is(target error)
方法来比较这个自定义类型。 - kaizenCoderCustomError.Is(target error)
方法来对这个自定义类型进行比较。 - undefined