【问题标题】:Error wrap/unwrap && type checking with errors.Is()Error wrap/unwrap && 使用errors.Is()进行类型检查
【发布时间】:2020-06-18 03:40:42
【问题描述】:

我正在检查 Go v1.13 Go v1.14 中的错误跟踪。为什么使用errors.Is() 似乎只能找到不带参数或带值接收器的错误实现?这意味着能够包装的错误实现必须有一个值接收器,以便能够通过errors.Is() 找到。

package main

import (
    "fmt"
    "errors"
)

type someAtomicError struct {}
func (e *someAtomicError) Error() string { return "Hi!" }
func checkAtomicError() {
    e := &someAtomicError{}
    e2 := fmt.Errorf("whoa!: %w", e)
    e2IsE := errors.Is(e2, &someAtomicError{})
    fmt.Println("atomic error trace ---\t\t", e2, "\t\t--- is traceable: ", e2IsE)
}


type someWrapperError struct {
    Msg string
    Err error
}
func (e someWrapperError) Error() string { return fmt.Sprintf("%s: %v", e.Msg, e.Err) }
func (e someWrapperError) Unwrap() error { return e.Err }
func checkWrapperError() {
    e := someWrapperError{"Hi!", nil}
    e2 := fmt.Errorf("whoa!: %w", e)
    e2IsE := errors.Is(e2, someWrapperError{"Hi!", nil})
    fmt.Println("wrapper error trace ---\t\t", e2, "\t--- is traceable: ", e2IsE)
}


type somePointerWrapperError struct {
    Msg string
    Err error
}
func (e *somePointerWrapperError) Error() string { return fmt.Sprintf("%s: %v", e.Msg, e.Err) }
func (e *somePointerWrapperError) Unwrap() error { return e.Err }
func checkPointerWrapperError() {
    e := &somePointerWrapperError{"Hi!", nil}
    e2 := fmt.Errorf("whoa!: %w", e)
    e2IsE := errors.Is(e2, &somePointerWrapperError{"Hi!", nil})
    fmt.Println("pointer wrapper error trace ---\t", e2, "\t--- is traceable: ", e2IsE)
}


func main() {
    checkAtomicError()
    checkWrapperError() 
    checkPointerWrapperError()
}

//atomic error trace ---         whoa!: Hi!         --- is traceable:  true
//wrapper error trace ---        whoa!: Hi!: <nil>  --- is traceable:  true
//pointer wrapper error trace ---    whoa!: Hi!: <nil>  --- is traceable:  false

https://play.golang.org/p/-hSukZ-gii2

似乎参数的任何差异,包括包装错误参数Err 中的任何差异,都将导致无法使用errors.Is() 找到类型。

【问题讨论】:

    标签: go error-handling wrapper trace


    【解决方案1】:

    您在尝试通过errors.Is 在另一个错误中查找一个错误时得到false 的原因是,虽然这两个错误可能具有相同的字段值,但它们是两个不同的内存指针:

    e  := &somePointerWrapperError{"Hi!", nil}
    e2 := &somePointerWrapperError{"Hi!", nil} // e2 != e
    
    ew := fmt.Errorf("whoa!: %w", e)
    
    errors.Is(ew, e)  // true
    errors.Is(ew, e2) // false - because `ew` wraps `e` not `e2`
    

    那么如何检测这种“类型”的错误获取它的值:改用errors.As

    e := &somePointerWrapperError{"Hi!", nil}
    e2 := fmt.Errorf("whoa!: %w", e)
    
    var ev *somePointerWrapperError
    
    if errors.As(e2, &ev) {
        fmt.Printf("%#v\n", ev) // &somePointerWrapperError{Msg:"Hi!", Err:error(nil)}
    }
    

    https://play.golang.org/p/CttKThLasXD

    【讨论】:

      【解决方案2】:

      远程相关,但也许对某人有帮助:我花了一些时间才意识到 errors.As(...) 实际上需要一个指向目标的双指针,而 errors.Is(...) 没有:

      var _ error = (*CustomError)(nil) // ensure CustomError implements error
      
      type CustomError struct {
          msg string
      }
      
      func (e CustomError) Error() string {
          return e.msg
      }
      
      func main() {
          err := &CustomError{"Hello, world!"} // Methods return pointers to errors, allowing them to be nil
          
          var eval *CustomError
      
          as := errors.As(err, &eval) // yes, that's **CustomError
          asFaulty := errors.As(err, eval) // no compile error, so it wrongly seems okay
          is := errors.Is(err, eval) // that's just *CustomError
      
          fmt.Printf("as: %t, asFaulty: %t, is: %t", as, asFaulty, is) // as: true, asFaulty: false, is: true
      }
      

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 2022-11-18
        • 2020-07-13
        • 2018-10-30
        • 1970-01-01
        • 2020-03-20
        • 1970-01-01
        • 2013-02-25
        相关资源
        最近更新 更多