donglun7151 2018-04-11 07:11
浏览 66
已采纳

如何在不导致nil值问题的情况下返回自定义错误

I have implemented a custom error type and do get strange behaviour with regard to nil values. When I do pass the custom error along as standard error interface, this is never recognized as nil, even if the custom error was returned as nil.

Have a look at this little test program:

package main

import (
    "fmt"
    "strconv"
)

type CustomError struct {
    Code int
}

func (e *CustomError) Error() string {
    return strconv.Itoa(e.Code)
}

func FailCustom(dofail bool) *CustomError {
    if dofail {
        return &CustomError{Code: 42}
    } else {
        return nil
    }
}

func WrapFailCustom(dofail bool) error {
    return FailCustom(dofail)
}

func main() {
    err := WrapFailCustom(false)
    if err == nil {
        fmt.Println("err is nil")
    } else {
        fmt.Println("err is not nil")
    }
}

Same in playground: https://play.golang.org/p/7bqeDw5B5fU

This does actually output "err is not nil".

I would have expected the nil value of type *CustomError to be implicitly cast to a nil value of type error. Can anybody explain to me, why this is not the case and how to correctly propagate nil values of custom error types?

Edit: Explanation for this can be found here as pointed out by Iain Duncan

To further explore the issue, let's consider the following modification to WrapFailCustom:

func WrapFailCustom(dofail bool) error {
    err := FailCustom(dofail)
    if err == nil {
        return nil
    } else {
        return err
    }
}

This does actually return "err is nil": https://play.golang.org/p/mEKJFyk5zqf

I do feel really bad with depending on this as solution, as it is easily forgotten when working with function that do spit out my custom error. Is there better way of making custom errors that does prevent this "ambiguity" from being possible to happen? The obvious solution of just using the basic error type all the time, seems really inconvenient for the code that consumes functions like WrapFailCustom, so I would like to avoid this...

  • 写回答

1条回答 默认 最新

  • duanning9110 2018-04-11 19:46
    关注

    For background, see Hiding nil values, understanding why golang fails here; and Go FAQ: Why is my nil error value not equal to nil?

    Go requires you to be explicit about types and conversions (e.g. you can't add a value of type int32 to a value of type int), but interface conversions and automatic interface value creation is exception to this rule. Whenever a value of interface type is required, you may use any value whose type implements (satisfies) the interface type, and the interface value will be automatically created for you.

    Your function:

    func WrapFailCustom(dofail bool) error {
        return FailCustom(dofail)
    }
    

    WrapFailCustom() has a return type error, and you try to return the result of the FailCustom() function, whose return type is *CustomError which is not the same as error! This should raise a red flag!

    What / how will be returned? An interface value of error type will be automatically created! *CustomError implements error, so it's all good, but as you experienced, if the pointer is nil, this implicit value wrapping will not result in an interface value being nil, but rather a non-nil interface value wrapping the value nil and the type *CustomError.

    The solution?

    Taking care of the root cause

    Is it really justified / does it have any value for FailCustom() to have a return type other than error? If not, easiest would be to deal with the "problem" right where it originates from:

    func FailCustom(dofail bool) error {
        if dofail {
            return &CustomError{Code: 42}
        }
        return nil
    }
    

    And then all your problems go away. If you follow the "Go way" to return errors using the error type, this is perfectly enough and satisfying. You don't even need the WrapFailCustom() function anymore.

    Manually accounting for it in WrapFailCustom()

    If you do need FailCustom() to return the custom *CustomError type, then you need to account for it "manually" in WrapFailCustom(). I would write it this way:

    func WrapFailCustom(dofail bool) error {
        if customErr := FailCustom(dofail); customErr != nil {
            return customErr 
        }
        return nil
    }
    

    (Note that I deliberately used a different customErr name and not err, signalling that it's not of type error, and care should be taken how it will be converted to error.)

    Using a custom error type that is an interface type

    If you want to / need to use your custom error type, another good approach would be to create an interface type that describes the "extra" functionality it incorporates:

    type CustomErr interface {
        Error // Embed error interface
        Code() int
    }
    

    And then we also need the implementation of this Code() method:

    func (e *CustomError) Code() int { return e.Code }
    

    What's the use of this?

    The root cause will be dealt with by returning a value of this interface type (and not a pointer):

    func FailCustom(dofail bool) CustomErr {
        if dofail {
            return &CustomError{Code: 42}
        }
        return nil
    }
    

    The implicit interface value will be created in FailCustom().

    Moreover, WrapFailCustom() becomes needless / useless. FailCustom() returns a value that is both an error, and you can get the Code from it using its Code() method. The returned value is aleady an interface value, and it is an error, you can use it where an error value is needed. The concrete type CustomError can even be made unexported (hidden).

    Related to this approach, check out Dave Cheney: Don’t just check errors, handle them gracefully, especially the section titled: Assert errors for behaviour, not type.

    本回答被题主选为最佳回答 , 对您是否有帮助呢?
    评论

报告相同问题?

悬赏问题

  • ¥15 前端echarts坐标轴问题
  • ¥15 CMFCPropertyPage
  • ¥15 ad5933的I2C
  • ¥15 请问RTX4060的笔记本电脑可以训练yolov5模型吗?
  • ¥15 数学建模求思路及代码
  • ¥50 silvaco GaN HEMT有栅极场板的击穿电压仿真问题
  • ¥15 谁会P4语言啊,我想请教一下
  • ¥15 这个怎么改成直流激励源给加热电阻提供5a电流呀
  • ¥50 求解vmware的网络模式问题 别拿AI回答
  • ¥24 EFS加密后,在同一台电脑解密出错,证书界面找不到对应指纹的证书,未备份证书,求在原电脑解密的方法,可行即采纳