GoSuda

Any ist nicht Any, aber Any ist Any

By snowmerak views ...

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.