Golang: Den Unterschied zwischen Nullzeigern und Nullschnittstellen verstehen

WBOY
Freigeben: 2024-07-18 08:51:19
Original
736 Leute haben es durchsucht

golang: Understanding the difference between nil pointers and nil interfaces

Ich habe ein wenig darüber nachgedacht, wie Null in go funktioniert und wie manchmal etwas gleichzeitig Null und nicht Null sein kann.

Hier ist ein kleines Beispiel für etwas, das ein Nullzeiger, aber keine Nullschnittstelle sein kann. Lassen Sie uns durchgehen, was das bedeutet.

Schnittstellen

Erstens verfügt Go über ein Konzept von Schnittstellen, die den Schnittstellen in einigen objektorientierten Sprachen ähneln, aber nicht ganz mit ihnen identisch sind (go ist nach den meisten Definitionen nicht OOP). In go ist eine Schnittstelle ein Typ, der Funktionen definiert, die ein anderer Typ implementieren muss, um die Schnittstelle zu erfüllen. Dadurch können wir über mehrere konkrete Typen verfügen, die eine Schnittstelle auf unterschiedliche Weise erfüllen können.

Fehler ist beispielsweise eine integrierte Schnittstelle mit einer einzigen Methode. Es sieht so aus:

type error interface {
    Error() string
}
Nach dem Login kopieren

Jeder Typ, der als Fehler verwendet werden soll, muss über eine Methode namens „Error“ verfügen, die eine Zeichenfolge zurückgibt. Beispielsweise könnte der folgende Code verwendet werden:

type ErrorMessage string

func (em ErrorMessage) Error() string {
    return string(em)
}

func DoSomething() error {
    // Try to do something, and it fails.
    if somethingFailed {
        var err ErrorMessage = "This failed"
        return err
    }
    return nil
}

func main() {
    err := DoSomething()
    if err != nil {
        panic(err)
    }
}
Nach dem Login kopieren

Beachten Sie in diesem Beispiel, dass DoSomething einen Fehler zurückgibt, wenn etwas schief geht. Wir können unseren ErrorMessage-Typ verwenden, da er über die Error-Funktion verfügt, die einen String zurückgibt, und daher die Fehlerschnittstelle implementiert.
Wenn kein Fehler aufgetreten ist, haben wir Null zurückgegeben.

Hinweise

In go zeigen Zeiger auf einen Wert, sie können aber auch auf keinen Wert zeigen. In diesem Fall ist der Zeiger Null. Zum Beispiel:

var i *int = nil

func main() {
    if i == nil {
        j := 5
        i = &j
    }
    fmt.Println("i is", *i)
}
Nach dem Login kopieren

In diesem Fall ist die i-Variable ein Zeiger auf einen int. Es beginnt als Nullzeiger, bis wir einen Int erstellen und ihn darauf verweisen.

Zeiger und Schnittstellen

Da an benutzerdefinierte Typen Funktionen (Methoden) angehängt sein können, können wir auch Funktionen für Zeiger auf Typen haben. Dies ist eine sehr verbreitete Praxis in Go. Das bedeutet auch, dass Zeiger auch Schnittstellen implementieren können. Auf diese Weise könnten wir einen Wert haben, der eine Nicht-Null-Schnittstelle, aber dennoch einen Nullzeiger ist. Betrachten Sie den folgenden Code:

type TruthGetter interface {
    IsTrue() bool
}

func PrintIfTrue(tg TruthGetter) {
    if tg == nil {
        fmt.Println("I can't tell if it's true")
        return
    }
    if tg.IsTrue() {
        fmt.Println("It's true")
    } else {
        fmt.Println("It's not true")
    }
}
Nach dem Login kopieren

Jeder Typ, der über eine IsTrue()-Bool-Methode verfügt, kann an PrintIfTrue übergeben werden, aber auch nil. Wir können also PrintIfTrue(nil) ausführen und es wird „Ich kann nicht sagen, ob es wahr ist“ ausgegeben.

Wir können auch so etwas Einfaches machen:

type Truthy bool

func (ty Truthy) IsTrue() bool {
    return bool(ty)
}

func main() {
    var ty Truthy = true
    PrintIfTrue(ty)
}
Nach dem Login kopieren

Dadurch wird „Es ist wahr“ gedruckt.

Oder wir können etwas Komplizierteres tun, wie zum Beispiel:

type TruthyNumber int

func (tn TruthyNumber) IsTrue() bool {
    return tn > 0
}

func main() {
    var tn TruthyNumber = -4
    PrintIfTrue(tn)
}
Nach dem Login kopieren

Das wird „Es ist nicht wahr“ ausdrucken. Keines dieser Beispiele ist ein Hinweis, daher gibt es bei keinem dieser Typen eine Chance auf eine Null, aber bedenken Sie Folgendes:

type TruthyPerson struct {
    FirstName string
    LastName string
}

func (tp *TruthyPerson) IsTrue() bool {
    return tp.FirstName != "" && tp.LastName != ""
}
Nach dem Login kopieren

In diesem Fall implementiert TruthGetter nicht TruthGetter, *TruthyPerson jedoch. Das sollte also funktionieren:

func main() {
    tp := &TruthyPerson{"Jon", "Grady"}
    PrintIfTrue(tp)
}
Nach dem Login kopieren

Das funktioniert, weil tp ein Zeiger auf eine TruthyPerson ist. Wenn der Zeiger jedoch Null ist, geraten wir in Panik.

func main() {
    var tp *TruthyPerson
    PrintIfTrue(tp)
}
Nach dem Login kopieren
Nach dem Login kopieren

Das wird Panik auslösen. In PrintIfTrue tritt die Panik jedoch nicht auf. Man könnte meinen, dass es in Ordnung ist, da PrintIfTrue nach Null sucht. Aber hier liegt das Problem. Es prüft Null gegen einen TruthGetter. Mit anderen Worten: Es wird nach einer Null-Schnittstelle gesucht, aber nicht nach einem Null-Zeiger. Und in func (tp *TruthyPerson) IsTrue() bool prüfen wir nicht auf eine Null. In go können wir immer noch Methoden auf einem Nullzeiger aufrufen, daher kommt es dort zu Panik. Die Lösung ist eigentlich ziemlich einfach.

func (tp *TruthyPerson) IsTrue() bool {
    if tp == nil {
        return false
    }
    return tp.FirstName != "" && tp.LastName != ""
}
Nach dem Login kopieren

Jetzt suchen wir nach einer Null-Schnittstelle in PrintIfTrue und nach einem Null-Zeiger in func (tp *TruthyPerson) IsTrue() bool. Und es wird jetzt „Es ist nicht wahr“ angezeigt. Wir können hier sehen, wie dieser gesamte Code funktioniert.

Bonus: Überprüfen Sie durch Nachdenken, ob beide Nullen gleichzeitig vorhanden sind

Mit Reflektion können wir eine kleine Änderung an PrintIfTrue vornehmen, sodass es sowohl nach Nullschnittstellen als auch nach Nullzeigern suchen kann. Hier ist der Code:

func PrintIfTrue(tg TruthGetter) {
    if tg == nil {
        fmt.Println("I can't tell if it's true")
        return
    }

    val := reflect.ValueOf(tg)
    k := val.Kind()
    if (k == reflect.Pointer || k == reflect.Chan || k == reflect.Func || k == reflect.Map || k == reflect.Slice) && val.IsNil() {
        fmt.Println("I can't tell if it's true")
        return
    }

    if tg.IsTrue() {
        fmt.Println("It's true")
    } else {
        fmt.Println("It's not true")
    }
}
Nach dem Login kopieren

Hier prüfen wir wie zuvor zunächst die Nullschnittstelle. Als nächstes verwenden wir Reflektion, um den Typ zu erhalten. Chan, Func, Map und Slice können neben Zeigern auch Null sein. Daher prüfen wir, ob der Wert einer dieser Typen ist, und wenn ja, prüfen wir, ob er Null ist. Und wenn ja, geben wir auch die Meldung „Ich kann nicht sagen, ob es wahr ist“ zurück. Das mag genau das sein, was Sie wollen, aber es ist eine Option. Mit dieser Änderung können wir Folgendes tun:

func main() {
    var tp *TruthyPerson
    PrintIfTrue(tp)
}
Nach dem Login kopieren
Nach dem Login kopieren

Möglicherweise sehen Sie manchmal einen Vorschlag für etwas Einfacheres, wie zum Beispiel:

// Don't do this
if tg == nil && reflect.ValueOf(tg).IsNil() {
    fmt.Println("I can't tell if it's true")
    return
}
Nach dem Login kopieren

Es gibt zwei Gründe, warum das nicht gut funktioniert. Erstens gibt es bei der Verwendung von Reflektion einen Leistungsaufwand. Wenn Sie die Verwendung von Reflexion vermeiden können, sollten Sie dies wahrscheinlich tun. Wenn wir zuerst nach der Nullschnittstelle suchen, müssen wir keine Reflexion verwenden, wenn es sich um eine Nullschnittstelle handelt.

Der zweite Grund ist, dass „reflect.Value.IsNil()“ in Panik gerät, wenn der Typ des Werts kein Typ ist, der Null sein kann. Deshalb fügen wir den Scheck für die Art hinzu. Wenn wir die Art nicht überprüft hätten, wären wir bei den Typen Truthy und TruthyNumber in Panik geraten.

Solange wir also sicherstellen, dass wir zuerst die Art überprüfen, wird jetzt „Ich kann nicht sagen, ob es wahr ist“ anstelle von „Es ist nicht wahr“ gedruckt. Abhängig von Ihrer Sichtweise kann dies eine Verbesserung sein. Hier ist der vollständige Code mit dieser Änderung.

Dies wurde ursprünglich auf Dan's Musings veröffentlicht

Das obige ist der detaillierte Inhalt vonGolang: Den Unterschied zwischen Nullzeigern und Nullschnittstellen verstehen. Für weitere Informationen folgen Sie bitte anderen verwandten Artikeln auf der PHP chinesischen Website!

Quelle:dev.to
Erklärung dieser Website
Der Inhalt dieses Artikels wird freiwillig von Internetnutzern beigesteuert und das Urheberrecht liegt beim ursprünglichen Autor. Diese Website übernimmt keine entsprechende rechtliche Verantwortung. Wenn Sie Inhalte finden, bei denen der Verdacht eines Plagiats oder einer Rechtsverletzung besteht, wenden Sie sich bitte an admin@php.cn
Beliebte Tutorials
Mehr>
Neueste Downloads
Mehr>
Web-Effekte
Quellcode der Website
Website-Materialien
Frontend-Vorlage
Über uns Haftungsausschluss Sitemap
Chinesische PHP-Website:Online-PHP-Schulung für das Gemeinwohl,Helfen Sie PHP-Lernenden, sich schnell weiterzuentwickeln!