Any ist nicht Any, aber Any ist Any

Any
In der Programmiersprache Go ist der Typ any ein Alias für interface{}, eine magisch anmutende Schnittstelle, die Werte beliebiger Typen aufnehmen kann. Anders ausgedrückt kann jeder Typ einer Variablen vom Typ any zugewiesen werden.
1var a any
2a = 42 // int
3a = "hello" // string
4a = true // bool
5a = 3.14 // float64
Auf diese Weise ermöglicht der Typ any, Werte verschiedener Typen aufzunehmen, und erlaubt so das Schreiben flexibler Code. Bei der Verwendung des Typs any sind jedoch einige Punkte zu beachten.
Type Assertion
Um den tatsächlichen Wert einer Variablen vom Typ any zu verwenden, muss eine Typzusicherung (type assertion) eingesetzt werden. Die Typzusicherung teilt dem Compiler mit, dass die Variable vom Typ any einen bestimmten Typ besitzt.
1var a any = "hello"
2str, ok := a.(string)
3if ok {
4 fmt.Println("String value:", str)
5} else {
6 fmt.Println("Not a string")
7}
In dem obigen Beispiel behauptet a.(string), dass a den Typ string besitzt. Falls a nicht vom Typ string ist, wird ok zu false, und ein Absturz des Programms in Form einer Panik kann auf diese Weise verhindert werden.
Type Switch
Der Type Switch (type switch) ermöglicht es, je nach dem tatsächlichen Typ einer Variablen vom Typ any unterschiedliche Operationen auszuführen. Dies ist nützlich, wenn mehrere Typen verarbeitet werden müssen.
1var a any = 42
2switch v := a.(type) {
3case int:
4 fmt.Println("Integer:", v)
5case string:
6 fmt.Println("String:", v)
7case bool:
8 fmt.Println("Boolean:", v)
9default:
10 fmt.Println("Unknown type")
11}
In dem obigen Beispiel prüft a.(type) den tatsächlichen Typ von a und führt den zu diesem Typ passenden Fall aus.
Reflection
Mit dem Paket reflect der Sprache Go lassen sich Typ und Wert einer Variablen vom Typ any zur Laufzeit dynamisch untersuchen und manipulieren. Dies ist beim Umgang mit komplexen Datenstrukturen nützlich.
1import (
2 "fmt"
3 "reflect"
4)
5
6var a any = 3.14
7v := reflect.ValueOf(a)
8fmt.Println("Type:", v.Type())
9fmt.Println("Value:", v.Float())
In dem obigen Beispiel werden mithilfe des Pakets reflect Typ und Wert von a zur Laufzeit ermittelt.
EFace
In der Sprache Go teilen sich Schnittstellen in zwei Hauptformen auf: EFace (Empty Interface) und IFace (Interface with Methods). Davon entspricht EFace dem Konzept des Typs any, das wir bisher behandelt haben.
Struktur
Da EFace eine besondere Struktur ist, die nur innerhalb der Laufzeit von Go existiert, werden wir hier ein Mimic (mimic) erstellen und damit arbeiten.
1type runtimeTypeMimic struct {
2 Size_ uintptr
3 PtrBytes uintptr // Anzahl der (Präfix-)Bytes im Typ, die Zeiger enthalten können
4 Hash uint32 // Hashwert des Typs; vermeidet Berechnungen in Hashtabellen
5 TFlag uint8 // zusätzliche Flags mit Typinformationen
6 Align_ uint8 // Ausrichtung einer Variablen dieses Typs
7 FieldAlign_ uint8 // Ausrichtung eines Struktur-Feldes dieses Typs
8 Kind_ uint8 // Aufzählung für C
9 Equal func(unsafe.Pointer, unsafe.Pointer) bool
10 GCData *byte
11 Str int32 // String-Form
12 PtrToThis int32 // Typ für einen Zeiger auf diesen Typ, kann null sein
13}
14
15type eFaceMimic struct {
16 Type *runtimeTypeMimic
17 Data unsafe.Pointer
18}
EFace besitzt zwei Felder:
Type: Ein Zeiger, der die Metadaten zum tatsächlichen Typ des Wertes enthält.Data: Ein Zeiger, der den tatsächlichen Wert enthält.
Außerdem verwaltet EFace die Typinformationen intern über die Struktur runtimeType. Diese Struktur enthält verschiedene Metadaten wie die Größe des Typs, seine Ausrichtung, seinen Hashwert usw.
Darin sind vor allem die Felder Kind_, Hash und Equal von Bedeutung. Kind_ gibt die Art des Typs an, während Hash und Equal in Hashtabellen zum Vergleichen von Typen und zur Berechnung von Hashwerten verwendet werden.
Kapselung
Lassen Sie uns nun dieses EFace kapseln, damit sich diese Low-Level-Techniken bequemer nutzen lassen.
1type TypeInfo struct {
2 Hash uint32
3 TFlag uint8
4 Kind uint8
5 Equal func(unsafe.Pointer, unsafe.Pointer) bool
6 This unsafe.Pointer
7}
8
9func GetTypeInfo(v any) TypeInfo {
10 eface := *(*eFaceMimic)(unsafe.Pointer(&v))
11 return TypeInfo{
12 Hash: eface.Type.Hash,
13 TFlag: eface.Type.TFlag,
14 Kind: eface.Type.Kind_,
15 Equal: eface.Type.Equal,
16 This: eface.Data,
17 }
18}
Die obige Funktion nimmt eine Variable vom Typ any entgegen, extrahiert daraus die interne Struktur EFace, entnimmt die Typinformationen und gibt sie als Struktur TypeInfo zurück. Mithilfe von TypeInfo lassen sich nun Hashwert, Art, Vergleichsfunktion usw. des Typs einfach abrufen.
Verwendung
Die Verwendung ist wirklich einfach. Man übergibt einfach eine Variable vom Typ any an die Funktion GetTypeInfo.
1func main() {
2 i := 42
3 eface := GetTypeInfo(i)
4 fmt.Printf("%+v\n", eface)
5
6 f := 3.14
7 eface2 := GetTypeInfo(f)
8 fmt.Printf("%+v\n", eface2)
9
10 // Die beiden Werte mithilfe der Equal-Funktion aus den Typinformationen vergleichen
11 log.Println(eface.Equal(eface.This, eface.This))
12 log.Println(eface2.Equal(eface2.This, eface2.This))
13 log.Println(eface2.Equal(eface.This, eface2.This))
14 log.Println(eface.Equal(eface2.This, eface.This))
15}
In dem obigen Beispiel werden ganzzahlige und Gleitkommawerte an die Funktion GetTypeInfo übergeben, deren Typinformationen ausgegeben und mithilfe der Funktion Equal verglichen. Auf diese Weise lassen sich Typinformationen schneller ermitteln und Typen vergleichen.
Darüber hinaus lässt sich diese Methode gleichermaßen auf Strukturen, Zeiger, Schnittstellen usw. anwenden, und sie kann auch das nicht seltene Problem des typed nil (typed nil) lösen. Denn Hash ist bei vorhandenem Typ zugewiesen, während This bei nil den Wert 0 besitzt.
Fazit
EFace spielt in der aktuellen Sprache Go eine wichtige Rolle bei der Handhabung des Typs any. Auf diese Weise lassen sich Werte verschiedener Typen flexibel und schnell verarbeiten sowie Typinformationen detailliert abrufen und nutzen. Allerdings handelt es sich dabei um eine innerhalb der Laufzeit verborgene Funktionalität, die von Änderungen der Sprachspezifikation betroffen sein kann; daher ist Vorsicht geboten.