GoSuda

Any ist nicht Any, aber Any ist Any

By snowmerak
views ...

Any

In der Sprache Go ist der Typ any ein Alias für das magische Interface interface{}, das jeden Typ aufnehmen kann. Das bedeutet, dass jeder Typ einer any-Typvariablen zugewiesen werden kann.

1var a any
2a = 42          // int
3a = "hello"     // string
4a = true        // bool
5a = 3.14        // float64

Der Typ any ermöglicht es somit, Werte verschiedener Typen aufzunehmen, was die Erstellung flexiblen Codes unterstützt. Bei der Verwendung des any-Typs sind jedoch einige Punkte zu beachten.

Type Assertion

Um den tatsächlichen Wert aus einer any-Typvariablen zu verwenden, muss eine Typzusicherung (type assertion) angewendet werden. Eine Typzusicherung teilt dem Compiler mit, dass eine any-Typvariable einem bestimmten Typ entspricht.

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}

Im obigen Beispiel wird durch a.(string) zugesichert, dass a vom Typ string ist. Sollte a nicht vom Typ string sein, wird ok auf false gesetzt, wodurch verhindert wird, dass das Programm in einen Panikzustand gerät.

Type Switch

Ein Type Switch ermöglicht es, unterschiedliche Aktionen basierend auf dem tatsächlichen Typ einer any-Typvariablen 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}

Im obigen Beispiel überprüft a.(type) den tatsächlichen Typ von a und führt den entsprechenden Case aus.

Reflection

Das reflect-Paket der Sprache Go ermöglicht die dynamische Überprüfung und Manipulation des Typs und des Wertes einer any-Typvariablen zur Laufzeit. Dies ist nützlich bei der Handhabung komplexer Datenstrukturen.

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())

Im obigen Beispiel können der Typ und der Wert von a zur Laufzeit mithilfe des reflect-Pakets überprüft werden.

EFace

In der Sprache Go werden Interfaces in zwei Hauptformen unterteilt: EFace (Empty Interface) und IFace (Interface with Methods). EFace ist identisch mit dem any-Typ, den wir bisher behandelt haben.

Struktur

EFace ist eine spezielle Struktur, die nur innerhalb der Go-Laufzeit existiert. Daher werden wir hier eine Nachbildung (mimic) erstellen.

 1type runtimeTypeMimic struct {
 2	Size_       uintptr
 3	PtrBytes    uintptr // number of (prefix) bytes in the type that can contain pointers
 4	Hash        uint32  // hash of type; avoids computation in hash tables
 5	TFlag       uint8   // extra type information flags
 6	Align_      uint8   // alignment of variable with this type
 7	FieldAlign_ uint8   // alignment of struct field with this type
 8	Kind_       uint8   // enumeration for C
 9	Equal       func(unsafe.Pointer, unsafe.Pointer) bool
10	GCData      *byte
11	Str         int32 // string form
12	PtrToThis   int32 // type for pointer to this type, may be zero
13}
14
15type eFaceMimic struct {
16	Type *runtimeTypeMimic
17	Data unsafe.Pointer
18}

Ein EFace besteht aus zwei Feldern:

  • Type: Ein Zeiger, der Metadaten über den tatsächlichen Typ des Wertes enthält.
  • Data: Ein Zeiger, der den tatsächlichen Wert enthält.

Intern verwaltet EFace Typinformationen über die runtimeType-Struktur. Diese Struktur enthält verschiedene Metadaten wie die Größe, Ausrichtung und den Hash-Wert des Typs.

Die hier hervorzuhebenden Felder sind Kind_, Hash und Equal. Kind_ repräsentiert die Art des Typs, während Hash und Equal zum Vergleichen von Typen und zur Berechnung von Hash-Werten in Hash-Tabellen verwendet werden.

Kapselung

Nun werden wir dieses EFace kapseln, um die Anwendung dieser "schwarzen Magie" zu vereinfachen.

 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}

Diese Funktion empfängt eine any-Typvariable, extrahiert die interne EFace-Struktur und entnimmt daraus die Typinformationen, die sie als TypeInfo-Struktur zurückgibt. Mit dieser TypeInfo können nun der Hash-Wert, die Art des Typs und die Vergleichsfunktion leicht abgerufen werden.

Anwendung

Die Anwendung ist denkbar einfach. Man übergibt einfach eine any-Typvariable 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	// Compare the two values using the Equal function from the type info
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}

Im obigen Beispiel werden Ganzzahlen und Gleitkommazahlen an die GetTypeInfo-Funktion übergeben, um deren Typinformationen auszugeben, und die Equal-Funktion wird verwendet, um die Werte zu vergleichen. Dies ermöglicht einen schnelleren Zugriff auf Typinformationen und einen schnelleren Typvergleich.

Darüber hinaus kann diese Methode auch für Strukturen, Zeiger und Interfaces verwendet werden und löst das nicht selten auftretende Problem des typisierten nil (typed nil), da Hash zugewiesen ist, wenn ein Typ vorhanden ist, während This 0 ist, wenn es nil ist.

Fazit

EFace spielt eine entscheidende Rolle im aktuellen Umgang mit dem any-Typ in Go. Es ermöglicht eine flexible und schnelle Verarbeitung von Werten unterschiedlicher Typen sowie einen detaillierten Abruf und die Nutzung von Typinformationen. Es ist jedoch zu beachten, dass es sich hierbei um eine in der Laufzeit verborgene Funktion handelt, die von Änderungen der Sprachspezifikation beeinflusst werden kann.