Anatomie von Schnittstellen in Go / Sudo Null IT News

Beim Kennenlernen von Go habe ich ein Beispiel in der Dokumentation gefunden:

func returnError() error { var p *MyError = nil if bad() { p = ErrBad } return p // Gibt immer einen Nicht-Null-Fehler zurück. }

Nachdem ich es mir angesehen hatte, fragte ich mich, warum returnError immer einen Nicht-Nicht-Null-Fehler zurückgibt?

Variablen in Go sind immer mit Werten initialisiert. Dies gilt auch für Schnittstellen. Es ist erwähnenswert, dass Schnittstellen als zwei Elemente implementiert werden: Typ (T) und Wert (V). Dies ist eine ziemlich oberflächliche Definition, die wir etwas weiter analysieren werden. Der Schnittstellenwert ist nur dann Null, wenn V und T beide Null sind.

Es gibt einen interessanten Punkt, nämlich den Fall, wenn V=nil und T!=nil. In diesem Fall helfen uns keine Schnittstellenprüfungen für nil. Und genau dieses Szenario passiert in returnsError .

Ich habe mich gefragt, wie genau diese Überprüfungen in Go funktionieren.

type Word struct { name string priority uint } type Foo interface { foo() } func (w *Wort) foo() { fmt.Println(“call foo()”) } func (w *Wort) noFoo() { fmt .Println(“call noFoo()”) } func call(f Foo) { if f != nil { f.foo() } else { fmt.Println(“f null”) } } func main() { var f1 *Wortruf(f1) }

Es gibt Überprüfungen im obigen Code. Alles funktioniert ohne Fehler. Die Ausgabe wird sein:

rufe foo()

Es scheint etwas seltsam, weil nil bestanden wurde und im Allgemeinen erwartet wurde, dass es entweder den Test für nil nicht bestehen würde oder bei der Ausführung feuern würde.
Finden wir es heraus.

Die Call-Funktion akzeptiert die Schnittstelle Foo. Wie wird der Word-Zeiger im Allgemeinen in die Foo-Schnittstelle konvertiert?
Vereinfachte Schnittstelle sieht so aus:

Schnittstelle:
1. statischer Typ
2. dynamische Informationen:
– Dynamischer Typ
– Dynamischer Wert

In unserem Beispiel ist der statische Typ „Foo“, der dynamische Typ „Word“ und der Wert „nil“.

Sehen wir uns das genauer an.

Sprachen mit Methoden können in zwei Arten unterteilt werden: Erstellen Sie Tabellen mit virtuellen Methoden (z. B. C++) oder führen Sie bei jedem Aufruf eine Methodensuche durch (Smalltalk). Im zweiten Fall wird Caching für Optimierungen verwendet. Go ist in der Mitte: Es hat eine Methodentabelle, wird aber zur Laufzeit berechnet.

Welche Schnittstellen gibt es in Go?

type iface struct { tab *itab data unsicher.Zeiger }

data ist direkt unser f1 aus dem Beispiel. Alle Informationen zu Methoden, Typen und anderen Dingen sind in der Registerkarte – Schnittstellentabelle versteckt.

type itab struct { inter *interfacetype _type *_type hash uint32 // Kopie von _type.hash. Wird für Typenschalter verwendet. _ [4]Byte Spaß [1]uintptr // variable Größe. Spaß[0]==0 bedeutet, dass _type inter nicht implementiert. }

Beachten Sie, dass itab einem statischen Schnittstellentyp (Foo) entspricht, nicht einem dynamischen (Word). Für eine größere Genauigkeit stimmt es mit dem Foo-Word-Paar überein. Daher gibt es innerhalb von itab nur Methoden, die die Foo-Schnittstelle erfüllen, und noFoo wird hier nicht vorkommen.

inter – enthält Metadaten über den statischen Typ der Schnittstelle.
_type – über den dynamischen Typ
fun – eine Reihe von Zeigern auf Schnittstellenmethoden. ist variabel groß.

Woher kommt itable?

itab – stellt eine Art Schlüsselwert dar, nämlich die Entsprechung eines statischen Typs zu einem dynamischen (erinnern Sie sich, dass Sie oben über Foo-Word gesprochen haben?). In großen Programmen gibt es viele Typen und Schnittstellen. Und nicht alle Kombinationen werden benötigt. Dazu erstellt der Go-Compiler mehrere Typbeschreibungstabellen. Die erste enthält eine Liste von Methoden für eine bestimmte Schnittstelle. Im zweiten, welche Methoden dynamische Typen enthalten. Unsere Itable ist die Entsprechung zwischen diesen beiden Tabellen. itable wird nach der Erstellung zwischengespeichert, sodass diese Übereinstimmung nur einmal ausgewertet werden muss.

Wie wird die Methode f.foo() aufgerufen und warum gibt es keinen Fehler?

Es wird etwas Analoges geben:

f.tab.fun[0](f.Daten)

Wir haben herausgefunden, was Spaß machen wird[0] (die einzige Methode unserer Foo-Schnittstelle) und welche Daten enthalten sind (das Word-Objekt selbst, im Beispiel haben wir einen nicht initialisierten Zeiger darauf übergeben – nil).
In manchen Sprachen (zum Beispiel C++) für Methoden gibt es so etwas wie this-Aufruf. Das bedeutet, dass der erste versteckte Parameter jeder Methode ein Zeiger auf das Objekt selbst ist (this). Und wenn Sie die Felder des Objekts ändern / lesen müssen, greifen wir implizit darüber zu. Gleichzeitig gibt es statische Methoden, die aufgerufen werden können, ohne ein Objekt zu erzeugen – für solche Methoden wird dies nicht implizit übergeben. nicht benötigt.
In unserem Beispiel passiert genau das. Die foo-Methode interagiert in keiner Weise mit Word. Und so wird f.data (das null ist) nicht verwendet und das Programm wird ordnungsgemäß beendet.

Wenn wir die Implementierung der Methode ändern, wird alles so funktionieren, wie es sollte:

func (w *Wort) foo() { fmt.Printf(“call foo(): %s\n”, w.name) }

Vorher gab es eine Aussage:
Der Schnittstellenwert ist nur dann Null, wenn V und T beide Null sind. Wenn wir jetzt über den Code sprechen: Der Wert der Schnittstelle wird nur dann null sein, wenn data und _type gleichzeitig gleich nil sind, denn die Option, wenn nur data gleich nil ist, ist ziemlich richtig. Ein Null-Schnittstellenwert, der per se keinen Wert enthält, ist nicht dasselbe wie ein Schnittstellenwert, der einen Nullzeiger enthält, wie wir bei der Betrachtung von Strukturen gesehen haben.

Vielen Dank für Ihre Aufmerksamkeit.

Similar Posts

Leave a Reply

Your email address will not be published. Required fields are marked *