web-dev-qa-db-fra.com

Vérifier les interfaces nulles et nulles dans Go

J'utilise actuellement cette fonction d'assistance pour vérifier les interfaces nulles et nulles

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

Puisque reflect.ValueOf(a).IsNil() panique si le type de la valeur est autre que Chan, Func, Map, Ptr, Interface ou Slice, j'ai ajouté la recover() différée pour les saisir.

Y a-t-il un meilleur moyen de réaliser cette vérification? Il pense qu'il devrait y avoir un moyen plus simple de procéder.

40
Erik Aigner

Voir par exemple la réponse de Kyle dans ce fil de discussion sur la liste de diffusion golang-nuts.

En bref: si vous ne stockez jamais (*T)(nil) dans une interface, vous pouvez utiliser la comparaison avec nil de manière fiable, sans avoir à utiliser la réflexion. Par contre, assigner nil sans type à une interface est toujours correct.

28
zzzz

Si aucune des options précédentes ne fonctionne pour vous, le mieux que j'ai pu faire jusqu'à présent est:

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

Au moins, il détecte des cas (* T) (nil).

7
aaa bbb

Deux solutions NE PAS utiliser la réflexion:

Copiez et collez le code dans l'éditeur à: https://play.golang.org/ à voir dans l'action.

1: Ajouter une fonction "IsInterfaceNil ()" à l'interface.

2: Utiliser un "type switch"

CODE PLUS BAS:

一一 一一 一一 一一 一一 一一 一一 一一 一一 一一

EXEMPLE N ° 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;
}

一一 一一 一一 一一 一一 一一 一一 一一 一一 一一

EXEMPLE N ° 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: Après l'implémentation dans ma base de code, j'ai trouvé # 2 (commutateur de type) comme la meilleure solution. Spécifiquement parce que je ne veux pas éditer la structure glfw.window dans la bibliothèque de liaisons que j'utilise. Voici un conteneur de mon cas d'utilisation ........ Toutes mes excuses pour mon style de codage non standard. https://Pastebin.com/22SUDeGG

2
J.M.I. MADISON

Voici la définition de l'interface pour cette solution exemple:

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
}

L'une de nos implémentations Checker prend en charge l'agrégation de Checkers. Mais les tests ont révélé le même problème que ce thread . Cette solution utilise le package reflect si la vérification nil simple échoue, en utilisant le type reflect.Value pour résoudre la 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
}
0
Ralph J. Jackson