在Go语言中检查nil和nil接口

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

Check for nil and nil interface in Go

问题

目前我正在使用这个辅助函数来检查nil和nil接口

func isNil(a interface{}) bool {
  defer func() { recover() }()
  return a == nil || reflect.ValueOf(a).IsNil()
}

由于reflect.ValueOf(a).IsNil()如果值的类型不是ChanFuncMapPtrInterfaceSlice,会引发panic,所以我加入了延迟的recover()来捕获这些情况。

有没有更好的方法来实现这个检查?我认为应该有更直接的方法来做这个。

英文:

Currently I'm using this helper function to check for nil and nil interfaces

func isNil(a interface{}) bool {
  defer func() { recover() }()
  return a == nil || reflect.ValueOf(a).IsNil()
}

Since reflect.ValueOf(a).IsNil() panics if the value's Kind is anything other than Chan, Func, Map, Ptr, Interface or Slice, I threw in the deferred recover() to catch those.

Is there a better way to achieve this check? It think there should be a more straight forward way to do this.

答案1

得分: 46

例如,在golang-nuts邮件列表的这个帖子中,可以看到Kyle的回答。

简而言之:如果你从未将(*T)(nil)存储在接口中,那么可以可靠地与nil进行比较,无需使用反射。另一方面,将无类型的nil赋给接口总是可以的。

英文:

See for example Kyle's answer in this thread at the golang-nuts mailing list.

In short: If you never store (*T)(nil) in an interface, then you can reliably use comparison against nil, no need to use reflection. On the other hand, assigning untyped nil to an interface is always OK.

答案2

得分: 31

如果之前的选项都不适用于您,到目前为止我能想到的最好的选择是:

if c == nil || (reflect.ValueOf(c).Kind() == reflect.Ptr && reflect.ValueOf(c).IsNil())

至少它可以检测到(*T)(nil)的情况。

英文:

If neither of the earlier options works for you, the best I could came up so far is:

if c == nil || (reflect.ValueOf(c).Kind() == reflect.Ptr && reflect.ValueOf(c).IsNil())

At least it detects (*T)(nil) cases.

答案3

得分: 10

两种不使用反射的解决方案:

将代码复制粘贴到编辑器中,网址为:https://play.golang.org/,以查看实际效果。

  1. 在接口中添加一个“IsInterfaceNil()”函数。
  2. 使用“类型开关”。

示例1:IsInterfaceNil()

//:示例 #1:
//:我更喜欢这种方法,因为
//:TakesInterface函数不需要知道
//:接口的所有不同实现。
package main;
import "fmt";

func main()(){

    var OBJ_OK *MyStruct = &( MyStruct{} );
    var NOT_OK *MyStruct = nil;
    
    //:将成功:
    TakesInterface( OBJ_OK );
    
    //:将失败:
    TakesInterface( NOT_OK );

}

func TakesInterface( input_arg MyInterface ){

    if( input_arg.IsInterfaceNil() ){
        panic("[InputtedInterfaceIsNil]");
    }
    
    input_arg.DoThing();
}

type MyInterface interface{
    DoThing()()
    IsInterfaceNil()(bool)
}
type MyStruct struct{}
func(f *MyStruct)DoThing()(){
    fmt.Println("[MyStruct.DoThing]");
}
func(f *MyStruct)IsInterfaceNil()(bool){
    if(nil==f){ return true; }
    return false;
}

示例2:类型开关

//:示例 #2:
//:这种方法也可以,但是接收接口的函数需要知道所有的实现。
//:这在一定程度上破坏了接口提供的与实现解耦的特性,
//:但如果你只是使用接口进行多态,这可能是可以接受的。(个人观点)
package main;
import "fmt";

func main()(){

    //:将成功:
    var OBJ_OK *IMPLMENTS_INTERFACE_01 = 
             &( IMPLMENTS_INTERFACE_01{} );
    TakesInterface( OBJ_OK );
    
    //:将失败:
    var NOT_OK *IMPLMENTS_INTERFACE_01 = nil;
    TakesInterface( NOT_OK );
}

func TakesInterface( hasDoThing MyInterface ){

    //:这将不起作用:
    if(nil==hasDoThing){
        panic("[This_Error_Message_Will_Never_Happen]");
    }
    
    //:类型开关拯救:
    switch v := hasDoThing.(type){
    
        case (*IMPLMENTS_INTERFACE_01): 
        if(nil==v){ panic("[Nil_PTR_01]"); }
        
        case (*IMPLMENTS_INTERFACE_02): 
        if(nil==v){ panic("[Nil_PTR_02]"); }
        
        case (*IMPLMENTS_INTERFACE_03): 
        if(nil==v){ panic("[Nil_PTR_03]"); }
        
        default: 
            panic("[UnsupportedInterface]");
    }
    
    hasDoThing.DoThing();
    
}

type IMPLMENTS_INTERFACE_01 struct{};
type IMPLMENTS_INTERFACE_02 struct{};
type IMPLMENTS_INTERFACE_03 struct{};
func (f *IMPLMENTS_INTERFACE_01)DoThing()(){
    fmt.Println( "DoingTheThing_01" );
}
func (f *IMPLMENTS_INTERFACE_02)DoThing()(){
    fmt.Println( "DoingTheThing_02" );
}
func (f *IMPLMENTS_INTERFACE_03)DoThing()(){
    fmt.Println( "DoingTheThing_03" );
}

type MyInterface interface{
    DoThing()()
}

更新:
在我的代码库中实施后,我发现#2(类型开关)是最好的解决方案。具体原因是我不想编辑我正在使用的绑定库中的glfw.Window结构体。这是我的用例的粘贴链接。
对于我非标准的编码风格,我表示歉意。https://pastebin.com/22SUDeGG

英文:

Two solutions NOT using reflection:

Copy and paste code into editor at: https://play.golang.org/ to see in action.

  1. Add an "IsInterfaceNil()" function to interface.
  2. Use A "type switch"

Example 1: IsInterfaceNil()

//:Example #1:
//:I prefer this method because the 
//:TakesInterface function does NOT need to know
//:about all the different implementations of
//:the interface.
package main;
import "fmt";

func main()(){

    var OBJ_OK *MyStruct = &( MyStruct{} );
    var NOT_OK *MyStruct = nil;
    
    //:Will succeed:
    TakesInterface( OBJ_OK );
    
    //:Will fail:
    TakesInterface( NOT_OK );

}

func TakesInterface( input_arg MyInterface ){

    if( input_arg.IsInterfaceNil() ){
        panic("[InputtedInterfaceIsNil]");
    }
    
    input_arg.DoThing();
}

type MyInterface interface{
    DoThing()()
    IsInterfaceNil()(bool)
}
type MyStruct struct{}
func(f *MyStruct)DoThing()(){
    fmt.Println("[MyStruct.DoThing]");
}
func(f *MyStruct)IsInterfaceNil()(bool){
    if(nil==f){ return true; }
    return false;
}

Example 2: Type Switch

//:Example #2:
//:This will also work, but the function taking
//:the interface needs to know about all 
//:implementations. This defeats a bit of the
//:decoupling from implementation that an
//:interface offers, but if you are just using
//:interfaces for polymorphism, it's probably
//:an okay way to go. (opinion)
package main;
import "fmt";

func main()(){

    //:Will succeed:
    var OBJ_OK *IMPLMENTS_INTERFACE_01 = 
             &( IMPLMENTS_INTERFACE_01{} );
    TakesInterface( OBJ_OK );
    
    //:Will fail:
    var NOT_OK *IMPLMENTS_INTERFACE_01 = nil;
    TakesInterface( NOT_OK );
}

func TakesInterface( hasDoThing MyInterface ){

    //:THIS WILL NOT WORK:
    if(nil==hasDoThing){
        panic("[This_Error_Message_Will_Never_Happen]");
    }
    
    //:TYPE SWITCH TO THE RESCUE:
    switch v := hasDoThing.(type){
    
        case (*IMPLMENTS_INTERFACE_01): 
        if(nil==v){ panic("[Nil_PTR_01]"); }
        
        case (*IMPLMENTS_INTERFACE_02): 
        if(nil==v){ panic("[Nil_PTR_02]"); }
        
        case (*IMPLMENTS_INTERFACE_03): 
        if(nil==v){ panic("[Nil_PTR_03]"); }
        
        default: 
            panic("[UnsupportedInterface]");
    }
    
    hasDoThing.DoThing();
    
}

type IMPLMENTS_INTERFACE_01 struct{};
type IMPLMENTS_INTERFACE_02 struct{};
type IMPLMENTS_INTERFACE_03 struct{};
func (f *IMPLMENTS_INTERFACE_01)DoThing()(){
    fmt.Println( "DoingTheThing_01" );
}
func (f *IMPLMENTS_INTERFACE_02)DoThing()(){
    fmt.Println( "DoingTheThing_02" );
}
func (f *IMPLMENTS_INTERFACE_03)DoThing()(){
    fmt.Println( "DoingTheThing_03" );
}

type MyInterface interface{
    DoThing()()
}

Update:
After implementing in my code base, I found #2 (type switch) to be best solution. Specifically because I DON'T want to EDIT the glfw.Window struct in the bindings library I am using. Here is a paste-bin of my use-case.
Apologies for my non-standard coding style. https://pastebin.com/22SUDeGG

答案4

得分: 2

对于Golang 1.16+

func IsNilish(val any) bool {
	if val == nil {
		return true
	}

	v := reflect.ValueOf(val)
	k := v.Kind()
	switch k {
	case reflect.Chan, reflect.Func, reflect.Map, reflect.Pointer,
		reflect.UnsafePointer, reflect.Interface, reflect.Slice:
		return v.IsNil()
	}

	return false
}
英文:

For Golang 1.16+

func IsNilish(val any) bool {
	if val == nil {
		return true
	}

	v := reflect.ValueOf(val)
	k := v.Kind()
	switch k {
	case reflect.Chan, reflect.Func, reflect.Map, reflect.Pointer,
		reflect.UnsafePointer, reflect.Interface, reflect.Slice:
		return v.IsNil()
	}

	return false
}

答案5

得分: 0

这是这个示例解决方案的接口定义:

package checker

import (
	"errors"

	"github.com/rs/zerolog"
)

var (
	// ErrNilChecker 在对空检查器调用 Check 时返回
	ErrNilChecker = errors.New("尝试使用空检查器进行检查")

	// ErrNilLogger 在提供空日志记录器的情况下返回 Check 函数
	ErrNilLogger = errors.New("为 Check 提供了空日志记录器")
)

// Checker 定义了接口
type Checker interface {
	Check(logger *zerolog.Logger) error
}

我们的一个 Checker 实现支持对 Checkers 进行聚合。但是测试发现与此线程相同的问题。
如果简单的空检查失败,此解决方案使用 reflect 包,利用 reflect.Value 类型来解决问题。

// AggregateChecker 实现了 Checker 接口,并支持报告应用每个检查器的结果
type AggregateChecker struct {
	checkers []Checker
}

func (ac *AggregateChecker) Add(aChecker Checker) error {
	if aChecker == nil {
		return ErrNilChecker
	}

	// 可能接口是一个类型化的空值
	// 例如:checker := (&MyChecker)(nil)
	t := reflect.TypeOf(aChecker)
	if reflect.ValueOf(aChecker) == reflect.Zero(t) {
		return ErrNilChecker
	}

	ac.checkers = append(ac.checkers, aChecker)
	return nil
}
英文:

This is the interface definition for this exmaple solution:

package checker

import (
	"errors"

	"github.com/rs/zerolog"
)

var (
	// ErrNilChecker returned if Check invoked on a nil checker
	ErrNilChecker = errors.New("attempted Check with nil Checker")

	// ErrNilLogger returned if the Check function is provide a nil logger
	ErrNilLogger = errors.New("nil logger provided for Check")
)

// Checker defines the interface
type Checker interface {
	Check(logger *zerolog.Logger) error
}

One of our Checker implementations supports aggregation of Checkers. But testing uncovered the same issue as this thread.
This solution uses the reflect package if the simple nil check fails, leveraging the reflect.Value type to resolve the question.

// AggregateChecker implements the Checker interface, and
// 	supports reporting the results of applying each checker
type AggregateChecker struct {
	checkers []Checker
}

func (ac *AggregateChecker) Add(aChecker Checker) error {
	if aChecker == nil {
		return ErrNilChecker
	}

	// It is possible the interface is a typed nil value
	// E.g. checker := (&MyChecker)(nil)
	t := reflect.TypeOf(aChecker)
	if reflect.ValueOf(aChecker) == reflect.Zero(t) {
		return ErrNilChecker
	}

	ac.checkers = append(ac.checkers, aChecker)
	return nil
}

答案6

得分: 0

如果您正在寻找一个全面的解决方案,这将是一个完美的选择
从https://github.com/thoas/go-funk采用

// IsEmpty返回对象是否被视为空。
func IsEmpty(obj interface{}) bool {
	if obj == nil || obj == "" || obj == false {
		return true
	}

	for _, v := range numericZeros {
		if obj == v {
			return true
		}
	}

	objValue := reflect.ValueOf(obj)

	switch objValue.Kind() {
	case reflect.Map:
		fallthrough
	case reflect.Slice, reflect.Chan:
		return objValue.Len() == 0
	case reflect.Struct:
		return reflect.DeepEqual(obj, ZeroOf(obj))
	case reflect.Ptr:
		if objValue.IsNil() {
			return true
		}

		obj = redirectValue(objValue).Interface()

		return reflect.DeepEqual(obj, ZeroOf(obj))
	}

	return false
}
英文:

If you are looking for a comprehensive solution this would be a perfect one
Adopted from https://github.com/thoas/go-funk

// IsEmpty returns if the object is considered as empty or not.
func IsEmpty(obj interface{}) bool {
	if obj == nil || obj == "" || obj == false {
		return true
	}

	for _, v := range numericZeros {
		if obj == v {
			return true
		}
	}

	objValue := reflect.ValueOf(obj)

	switch objValue.Kind() {
	case reflect.Map:
		fallthrough
	case reflect.Slice, reflect.Chan:
		return objValue.Len() == 0
	case reflect.Struct:
		return reflect.DeepEqual(obj, ZeroOf(obj))
	case reflect.Ptr:
		if objValue.IsNil() {
			return true
		}

		obj = redirectValue(objValue).Interface()

		return reflect.DeepEqual(obj, ZeroOf(obj))
	}

	return false
}

答案7

得分: 0

与AH.Pooladvand的答案类似,但只检查是否为nil

func IsNil(input interface{}) bool {
	if input == nil {
		return true
	}
	kind := reflect.ValueOf(input).Kind()
	switch kind {
	case reflect.Ptr, reflect.Map, reflect.Slice, reflect.Chan:
		return reflect.ValueOf(input).IsNil()
	default:
		return false
	}
}
英文:

Similar to AH.Pooladvand's answer, but only check if it is nil.

func IsNil(input interface{}) bool {
	if input == nil {
		return true
	}
	kind := reflect.ValueOf(input).Kind()
	switch kind {
	case reflect.Ptr, reflect.Map, reflect.Slice, reflect.Chan:
		return reflect.ValueOf(input).IsNil()
	default:
		return false
	}
}

答案8

得分: -1

func main() {
	var foo interface{}
	fmt.Println(reflect.TypeOf(foo) == nil) // true

	type Bar struct{}
	var bar *Bar
	fmt.Println(reflect.TypeOf(bar) != nil) // true
}
英文:
func main() {
	var foo interface{}
	fmt.Println(reflect.TypeOf(foo) == nil) // true

	type Bar struct{}
	var bar *Bar
	fmt.Println(reflect.TypeOf(bar) != nil) // true
}

答案9

得分: -1

也许你尝试在填充接口的函数中使用了一个错误:

包a

func getObj() (obj *someObject, err error) {
   obj = db.getA()
   if obj == nil {
      err = fmt.Errorf("Some error")
   }
   return
}

包b

import a

var i interface{}

i, err = a.getObj()
if err != nil {
   // 捕获错误
} else {
   // 进行下一步操作
}
英文:

May be you try use an error in the function that populates the interface:

package a

func getObj() (obj *someObject, err error) {
   obj = db.getA()
   if obj == nil {
      err = fmt.Errorf("Some error")
   }
   return
}

package b

import a

var i interface{}

i, err = a.getObj()
if err != nil {
   // catch err
} else {
   // do next step
}

答案10

得分: -2

考虑inboundData作为你的接口

使用len()函数来检查接口中是否有数据

if inboundData != nil && len(inboundData) > 0 {
    // ... 做一些事情 | 数据存在
} else {
    // ... 数据不存在
}
英文:

consider inboundData to be your interface

use the len() function to check if there are data in the interface

if inboundData != nil && len(inboundData) > 0 {
    // ... do stuff | data is present
} else {
    // ... data is not present
}

huangapple
  • 本文由 发表于 2012年11月20日 23:21:54
  • 转载请务必保留本文链接:https://go.coder-hub.com/13476349.html
匿名

发表评论

匿名网友

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

确定