Hallo Habr! Ich präsentiere Ihnen die Übersetzung des Artikels 
"Die Gesetze der Reflexion" vom Schöpfer der Sprache.
Reflexion ist die Fähigkeit eines Programms, seine eigene Struktur zu erkunden, insbesondere durch Typen. Dies ist eine Form der Metaprogrammierung und eine große Quelle der Verwirrung.
In Go wird Reflexion beispielsweise in den Test- und FMT-Paketen häufig verwendet. In diesem Artikel werden wir versuchen, "Magie" loszuwerden, indem wir erklären, wie Reflexion in Go funktioniert.
Typen und Schnittstellen
Da die Reflexion auf einem Typsystem basiert, aktualisieren wir unser Wissen über Typen in Go.
Go ist statisch typisiert. Jede Variable hat einen und nur einen statischen Typ, der zur Kompilierungszeit festgelegt wurde: 
int, float32, *MyType, []byte ... Wenn wir deklarieren:
 type MyInt int var i int var j MyInt 
dann ist 
i vom Typ 
int und 
j vom Typ 
MyInt . Die Variablen 
i und 
j haben unterschiedliche statische Typen, und obwohl sie denselben Basistyp haben, können sie ohne Konvertierung nicht einander zugewiesen werden.
Eine der wichtigen Typkategorien sind Schnittstellen, bei denen es sich um feste Methodensätze handelt. Eine Schnittstelle kann einen bestimmten Wert (ohne Schnittstelle) speichern, solange dieser Wert die Methoden der Schnittstelle implementiert. Ein bekanntes Beispielpaar sind 
io.Reader und io.Writer , die 
Reader- und Writer-Typen aus dem 
io-Paket :
 
Es wird gesagt, dass jeder Typ, der die 
Read() oder 
Write() -Methode mit dieser Signatur implementiert, 
io.Reader bzw. 
io.Writer implementiert. Dies bedeutet, dass eine Variable vom Typ 
io.Reader einen beliebigen Wert vom Typ Read () enthalten kann:
 var r io.Reader r = os.Stdin r = bufio.NewReader(r) r = new(bytes.Buffer) 
Es ist wichtig zu verstehen, dass 
r jedem Wert zugewiesen werden kann, der 
io.Reader implementiert. Go ist statisch typisiert und der statische Typ 
r ist 
io.Reader .
Ein äußerst wichtiges Beispiel für einen Schnittstellentyp ist die leere Schnittstelle:
 interface{} 
Es ist eine leere Menge von ∅ Methoden und wird durch einen beliebigen Wert implementiert.
Einige sagen, Go-Schnittstellen seien dynamisch typisierte Variablen, aber dies ist ein Irrtum. Sie sind statisch typisiert: Eine Variable mit einem Schnittstellentyp hat immer denselben statischen Typ, und obwohl zur Laufzeit der in der Schnittstellenvariablen gespeicherte Wert den Typ ändern kann, erfüllt dieser Wert immer die Schnittstelle. (Keine 
undefined , 
NaN oder andere Dinge, die die Programmlogik brechen.)
Dies muss verstanden werden - Reflexion und Schnittstellen sind eng miteinander verbunden.
Interne Darstellung der Schnittstelle
Russ Cox schrieb einen ausführlichen 
Blog-Beitrag über das Einrichten einer Benutzeroberfläche in Go. Nicht weniger guter Artikel 
ist auf Habr'e . Es ist nicht nötig, die ganze Geschichte zu wiederholen, die Hauptpunkte werden erwähnt.
Eine Schnittstellestypvariable enthält ein Paar: den der Variablen zugewiesenen spezifischen Wert und einen Typdeskriptor für diesen Wert. Genauer gesagt ist der Wert das grundlegende Datenelement, das die Schnittstelle implementiert, und der Typ beschreibt den vollständigen Typ dieses Elements. Zum Beispiel nach
 var r io.Reader tty, err := os.OpenFile("/dev/tty", os.O_RDWR, 0) if err != nil { return nil, err } r = tty 
r enthält schematisch ein Paar 
(, ) --> (tty, *os.File) . Beachten Sie, dass der Typ 
*os.File andere Methoden als 
Read() implementiert. Selbst wenn der Schnittstellenwert nur Zugriff auf die Read () -Methode bietet, enthält der darin enthaltene Wert alle Informationen über den Typ dieses Werts. Deshalb können wir solche Dinge tun:
 var w io.Writer w = r.(io.Writer) 
Der Ausdruck in dieser Zuweisung ist eine Typanweisung. es behauptet, dass das Element in 
r auch 
io.Writer implementiert, und deshalb können wir es 
w zuweisen. Nach der Zuweisung enthält 
w ein Paar 
(tty, *os.File) . Dies ist das gleiche Paar wie in 
r . Der statische Typ der Schnittstelle bestimmt, welche Methoden für die Schnittstellenvariable aufgerufen werden können, obwohl ein größerer Satz von Methoden einen bestimmten Wert enthalten kann.
Weiter können wir Folgendes tun:
 var empty interface{} empty = w 
und der leere Wert des leeren Feldes enthält wieder dasselbe Paar 
(tty, *os.File) . Dies ist praktisch: Eine leere Schnittstelle kann einen beliebigen Wert und alle Informationen enthalten, die wir jemals benötigen werden.
Wir brauchen hier keine Typzusicherung, da bekannt ist, dass 
w eine leere Schnittstelle erfüllt. In dem Beispiel, in dem wir den Wert vom 
Reader zum 
Writer , mussten wir explizit eine Typzusicherung verwenden, da die 
Writer Methoden keine Teilmenge der 
Reader Writer Methoden sind. Der Versuch, einen Wert zu konvertieren, der nicht mit der Schnittstelle übereinstimmt, führt zu Panik.
Ein wichtiges Detail ist, dass ein Paar innerhalb einer Schnittstelle immer ein Formular (Wert, bestimmter Typ) hat und kein Formular (Wert, Schnittstelle) haben kann. Schnittstellen unterstützen keine Schnittstellen als Werte.
Jetzt sind wir bereit zu reflektieren.
Das erste Gesetz der Reflexion reflektieren
- Die Reflexion erstreckt sich von der Schnittstelle bis zur Reflexion des Objekts.
Auf einer grundlegenden Ebene ist Reflect nur ein Mechanismus zum Untersuchen eines Paares von Typ und Wert, das in einer Schnittstellenvariablen gespeichert ist. Zu Beginn müssen wir zwei Typen kennen: 
reflect.Type und 
reflect.Value . Diese beiden Typen bieten Zugriff auf den Inhalt der Schnittstellenvariablen und werden von den einfachen Funktionen Reflect.TypeOf () bzw. Reflect.ValueOf () zurückgegeben. Sie extrahieren Teile aus der Bedeutung der Schnittstelle. (Außerdem ist 
reflect.Value leicht zu 
reflect.Type , aber lassen Sie uns die Konzepte von 
Value und 
Type im Moment nicht mischen.)
Beginnen wir mit 
TypeOf() :
 package main import ( "fmt" "reflect" ) func main() { var x float64 = 3.4 fmt.Println("type:", reflect.TypeOf(x)) } 
Das Programm wird ausgegeben
type: float64Das Programm ähnelt der Übergabe einer einfachen Variablen 
float64 x an 
reflect.TypeOf() . Sehen Sie die Schnittstelle? Und es ist - 
reflect.TypeOf() akzeptiert eine leere Schnittstelle gemäß der Funktionsdeklaration:
 
Wenn wir 
reflect.TypeOf(x) aufrufen, wird 
x zuerst in einer leeren Schnittstelle gespeichert, die dann als Argument übergeben wird. 
reflect.TypeOf() entpackt diese leere Schnittstelle, um Typinformationen wiederherzustellen.
Die Funktion 
reflect.ValueOf() stellt natürlich den Wert wieder her (im Folgenden werden wir die Vorlage ignorieren und uns auf den Code konzentrieren):
 var x float64 = 3.4 fmt.Println("value:", reflect.ValueOf(x).String()) 
wird gedruckt
value: <float64 Value>(Wir rufen die 
String() -Methode explizit auf, da das fmt-Paket standardmäßig 
reflect.Value , um den Wert zu 
reflect.Value Wert und druckt einen bestimmten Wert.)
Sowohl 
reflect.Type als auch 
reflect.Value verfügen über viele Methoden, mit denen Sie sie untersuchen und ändern können. Ein wichtiges Beispiel ist das 
reflect.Value verfügt über eine 
Type() -Methode, die den 
reflect.Value zurückgibt. 
reflect.Type und 
reflect.Value haben eine 
Kind() -Methode, die eine Konstante 
Uint, Float64, Slice welches primitive Element gespeichert ist: 
Uint, Float64, Slice ... Diese Konstanten werden in der Aufzählung im 
Uint, Float64, Slice Paket deklariert. 
Value mit Namen wie 
Int() und 
Float() ermöglichen es uns, darin enthaltene Werte (wie int64 und float64) herauszuholen:
 var x float64 = 3.4 v := reflect.ValueOf(x) fmt.Println("type:", v.Type()) fmt.Println("kind is float64:", v.Kind() == reflect.Float64) fmt.Println("value:", v.Float()) 
wird gedruckt
 type: float64 kind is float64: true value: 3.4 
Es gibt auch Methoden wie 
SetInt() und 
SetFloat() , aber um sie zu verwenden, müssen wir die Einstellbarkeit verstehen, das Thema des dritten Reflexionsgesetzes.
Die Reflect-Bibliothek verfügt über einige Eigenschaften, die Sie hervorheben müssen. 
int64 die API einfach zu halten, wirken die 
int64 "getter" und "setter" auf den größten Typ, der einen Wert enthalten kann: 
int64 für alle 
int64 Ganzzahlen. Das heißt, die 
Int() -Methode des 
Value Werts gibt 
int64 , und der 
SetInt() -Wert nimmt 
int64 . Möglicherweise ist eine Konvertierung in den tatsächlichen Typ erforderlich:
 var x uint8 = 'x' v := reflect.ValueOf(x) fmt.Println("type:", v.Type()) fmt.Println("kind is uint8: ", v.Kind() == reflect.Uint8) x = uint8(v.Uint())  
wird sein
 type: uint8 kind is uint8: true 
Hier gibt 
v.Uint() uint64 , eine explizite 
uint64 ist erforderlich.
Die zweite Eigenschaft ist, dass die 
Kind() Reflektion des Objekts den Basistyp und nicht den statischen Typ beschreibt. Wenn das Reflektionsobjekt einen Wert eines benutzerdefinierten Ganzzahltyps enthält, wie in
 type MyInt int var x MyInt = 7 v := reflect.ValueOf(x)  
v.Kind() == reflect.Int , obwohl der statische Typ von 
x MyInt , nicht 
int . Mit anderen Worten, 
Kind() kann im 
MyInt Type() int von 
MyInt unterscheiden. 
Kind kann nur Werte von integrierten Typen akzeptieren.
Das zweite Reflexionsgesetz reflektiert
- Die Reflexion erstreckt sich vom reflektierenden Objekt zur Schnittstelle.
Wie bei der physischen Reflexion erzeugt das Reflektieren in Go das Gegenteil.
Mit 
reflect.Value können wir den Wert der Schnittstelle mithilfe der 
Interface() -Methode wiederherstellen. Die Methode packt die Typ- und Wertinformationen zurück in die Schnittstelle und gibt das Ergebnis zurück:
 
bvt
Als Beispiel:
 y := v.Interface().(float64)  
float64 den Wert von 
float64 der durch das 
float64 Objekt 
v .
Wir können es jedoch noch besser machen. Die Argumente in 
fmt.Println() und 
fmt.Printf() werden als leere Schnittstellen übergeben, die dann wie in den vorherigen Beispielen vom fmt-Paket intern entpackt werden. Daher ist alles, was zum korrekten Drucken des Inhalts von 
reflect.Value erforderlich ist, das 
reflect.Value des Ergebnisses der 
Interface() -Methode an die formatierte Ausgabefunktion:
 fmt.Println(v.Interface()) 
(Warum nicht 
fmt.Println(v) ? Da 
v vom Typ 
reflect.Value , möchten wir den darin enthaltenen Wert erhalten.) Da unser Wert 
float64 , können wir sogar das Gleitkommaformat verwenden, wenn wir möchten:
 fmt.Printf("value is %7.1e\n", v.Interface()) 
wird in einem bestimmten Fall ausgegeben
3.4e+00Auch hier muss 
v.Interface() Ergebnistyp 
v.Interface() in 
float64 . Ein leerer Schnittstellenwert enthält Informationen zu einem bestimmten Wert und wird von 
fmt.Printf() wiederhergestellt.
Kurz gesagt, die 
Interface() -Methode ist die Umkehrung der 
ValueOf() -Funktion, mit der Ausnahme, dass das Ergebnis immer vom statischen Typ 
interface{} .
Wiederholen: Die Reflexion erstreckt sich von Schnittstellenwerten zu Reflexionsobjekten und umgekehrt.
Drittes Gesetz der Reflexionsreflexion
- Um das Reflexionsobjekt zu ändern, muss der Wert einstellbar sein.
Das dritte Gesetz ist das subtilste und verwirrendste. Wir beginnen mit den ersten Prinzipien.
Dieser Code funktioniert nicht, verdient jedoch Aufmerksamkeit.
 var x float64 = 3.4 v := reflect.ValueOf(x) v.SetFloat(7.1)  
Wenn Sie diesen Code ausführen, stürzt er vor Panik mit einer kritischen Meldung ab:
panic: reflect.Value.SetFloatDas Problem ist nicht, dass das Literal 
7.1 nicht angesprochen wird; Dies ist, was 
v nicht installierbar ist. 
reflect.Value ist eine Eigenschaft von 
reflect.Value , und nicht jeder 
reflect.Value hat sie.
Die 
reflect.Value.CanSet() -Methode gibt 
reflect.Value.CanSet() an. in unserem Fall:
 var x float64 = 3.4 v := reflect.ValueOf(x) fmt.Println("settability of v:", v.CanSet()) 
wird drucken:
settability of v: falseBeim Aufrufen der 
Set() -Methode für einen nicht verwalteten Wert ist ein Fehler aufgetreten. Aber was ist Installierbarkeit?
Nachhaltigkeit ist ein bisschen wie Adressierbarkeit, aber strenger. Dies ist eine Eigenschaft, bei der das Reflexionsobjekt den gespeicherten Wert ändern kann, der zum Erstellen des Reflexionsobjekts verwendet wurde. Die Nachhaltigkeit wird dadurch bestimmt, ob das Reflexionsobjekt das Quellelement oder nur eine Kopie davon enthält. Wenn wir schreiben:
 var x float64 = 3.4 v := reflect.ValueOf(x) 
Wir übergeben eine Kopie von 
x an 
reflect.ValueOf() , daher wird die Schnittstelle als Argument für 
reflect.ValueOf() - dies ist eine Kopie von 
x , nicht von 
x selbst. Also, wenn die Aussage:
 v.SetFloat(7.1) 
Wenn es ausgeführt würde, würde es 
x nicht aktualisieren, obwohl 
v aussieht, als wäre es aus 
x . Stattdessen würde er die Kopie von 
x aktualisieren, die im Wert von 
v gespeichert ist, und 
x selbst wäre nicht betroffen. Dies ist verboten, um keine Probleme zu verursachen, und die Installierbarkeit ist eine Eigenschaft, mit der ein Problem verhindert wird.
Das sollte nicht seltsam erscheinen. Dies ist eine häufige Situation in ungewöhnlichen Kleidern. Überlegen Sie, ob Sie 
x an eine Funktion übergeben 
x :
f(x)Wir erwarten nicht, dass 
f() x ändern kann, da wir eine Kopie des Werts von 
x , nicht 
x selbst. Wenn 
f() x direkt ändern soll, müssen wir einen Zeiger auf 
x an unsere Funktion übergeben:
f(&x)Dies ist unkompliziert und vertraut, und die Reflexion funktioniert ähnlich. Wenn wir 
x mithilfe der Reflexion ändern möchten, müssen wir der Reflexionsbibliothek einen Zeiger auf den Wert bereitstellen, den wir ändern möchten.
Lass es uns tun. Zuerst initialisieren wir 
x wie gewohnt und erstellen dann ein 
reflect.Value p , das darauf zeigt.
 var x float64 = 3.4 p := reflect.ValueOf(&x)  
wird ausgegeben
type of p: *float64
settability of p: falseDas Reflexionsobjekt 
p kann nicht gesetzt werden, aber es ist nicht das 
p , das wir setzen wollen, es ist der Zeiger 
*p . Um 
Value.Elem() , auf was 
p zeigt, rufen wir die 
Value.Elem() -Methode auf, die den Wert indirekt über den Zeiger 
reflect.Value v und das Ergebnis in 
reflect.Value v speichert:
 v := p.Elem() fmt.Println("settability of v:", v.CanSet()) 
Jetzt ist 
v ein installierbares Objekt.
settability of v: trueund da es 
x , können wir endlich 
v.SetFloat() , um den Wert von 
x zu ändern:
 v.SetFloat(7.1) fmt.Println(v.Interface()) fmt.Println(x) 
Schlussfolgerung wie erwartet
7.1
7.1Reflektieren mag schwer zu verstehen sein, aber es macht genau das, was die Sprache macht, wenn auch mit Hilfe von 
reflect.Type und 
reflection.Value reflect.Type , der verbergen kann, was passiert. Denken Sie daran, dass 
reflection.Value die Adresse einer Variablen benötigt, um sie zu ändern.
Strukturen
In unserem vorherigen Beispiel war 
v kein Zeiger, sondern wurde nur daraus abgeleitet. Ein üblicher Weg, um diese Situation zu schaffen, besteht darin, mithilfe von Reflexion Strukturfelder zu ändern. Solange wir die Adresse der Struktur haben, können wir ihre Felder ändern.
Hier ist ein einfaches Beispiel, das den Wert der Struktur 
t analysiert. Wir erstellen ein Reflexionsobjekt mit der Adresse der Struktur, um es später zu ändern. Setzen Sie dann typeOfT auf seinen Typ und durchlaufen Sie die Felder mit einfachen Methodenaufrufen ( 
eine detaillierte Beschreibung finden Sie im 
Paket ). Beachten Sie, dass wir Feldnamen aus dem Strukturtyp extrahieren, die Felder selbst jedoch regelmäßig 
reflect.Value .
 type T struct { A int B string } t := T{23, "skidoo"} s := reflect.ValueOf(&t).Elem() typeOfT := s.Type() for i := 0; i < s.NumField(); i++ { f := s.Field(i) fmt.Printf("%d: %s %s = %v\n", i, typeOfT.Field(i).Name, f.Type(), f.Interface()) } 
Das Programm wird ausgegeben
0: A int = 23
1: B string = skidooEin weiterer Punkt zur Installierbarkeit wird hier gezeigt: Die Namen der 
T Felder in Großbuchstaben (exportiert), da nur exportierte Felder einstellbar sind.
Da 
s ein installierbares Reflexionsobjekt enthält, können wir das Strukturfeld ändern.
 s.Field(0).SetInt(77) s.Field(1).SetString("Sunset Strip") fmt.Println("t is now", t) 
Ergebnis:
t is now {77 Sunset Strip}Wenn wir das Programm so ändern, dass 
s aus 
t anstelle von 
&t , 
SetInt() die Aufrufe von 
SetInt() und 
SetString() in Panik, da die Felder 
t nicht einstellbar wären.
Fazit
Erinnern Sie sich an die Gesetze der Reflexion:
- Die Reflexion erstreckt sich von der Schnittstelle bis zur Reflexion des Objekts.
- Die Reflexion erstreckt sich von der Reflexion eines Objekts bis zur Schnittstelle.
- Um das Reflexionsobjekt zu ändern, muss der Wert festgelegt werden.
Gepostet von 
Rob Pike .