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 Variablen vom Typ any zugewiesen werden kann.

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

Wie gezeigt, kann der Typ any Werte verschiedener Typen speichern, was die Erstellung flexiblen Codes ermöglicht. Bei der Verwendung des Typs any sind jedoch einige Punkte zu beachten.

Type Assertion

Um den tatsächlichen Wert einer any-Typ-Variablen zu verwenden, muss eine Typzusicherung (type assertion) verwendet werden. Eine Typzusicherung teilt dem Compiler mit, dass eine any-Typ-Variable einen bestimmten Typ hat.

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 behauptet a.(string), dass a vom Typ string ist. Falls a nicht vom Typ string ist, wird ok false, wodurch ein Programmabsturz verhindert werden kann.

Type Switch

Ein Typ-Switch (type switch) ermöglicht es, je nach dem tatsächlichen Typ einer any-Typ-Variablen unterschiedliche Aktionen 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 Fall aus.

Reflection

Mit dem reflect-Paket der Sprache Go können Typ und Wert einer any-Typ-Variablen zur Laufzeit dynamisch überprüft und manipuliert werden. Dies ist nützlich beim Umgang mit komplexen 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 Typ und Wert von a zur Laufzeit mit dem reflect-Paket überprüft werden.

EFace

In der Sprache Go werden Interfaces in zwei Hauptformen unterteilt: EFace (Empty Interface) und IFace (Interface with Methods). EFace entspricht dem Konzept des any-Typs, den wir bisher behandelt haben.

Struktur

EFace ist eine spezielle Struktur, die nur innerhalb der Go-Laufzeitumgebung existiert. Daher werden wir hier einen Mimic erstellen, um sie zu behandeln.

 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 Pointer, der Metadaten über den tatsächlichen Typ des Wertes enthält.
  • Data: Ein Pointer, der den tatsächlichen Wert enthält.

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

Die Felder Kind_, Hash und Equal sind hier von besonderer Bedeutung. Kind_ gibt die Art des Typs an, während Hash und Equal zum Vergleichen von Typen und zum Berechnen von Hash-Werten in Hash-Tabellen verwendet werden.

Kapselung

Lassen Sie uns nun dieses EFace kapseln, um die Anwendung von "schwarzer Magie" zu erleichtern.

 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 die interne EFace-Struktur und entnimmt daraus die Typinformationen, die sie als TypeInfo-Struktur zurückgibt. Mit dieser TypeInfo kann nun einfach auf den Hash-Wert, die Art und die Vergleichsfunktion des Typs zugegriffen werden.

Anwendung

Die Anwendung ist denkbar einfach. Man übergibt einfach die any-Typ-Variable 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 eine Ganzzahl und ein Gleitkommawert an die Funktion GetTypeInfo ü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 den Vergleich von Typen.

Diese Methode kann auch für Strukturen, Pointer und Interfaces verwendet werden und löst das häufig auftretende Problem des "typed nil". Der Hash wird zugewiesen, wenn ein Typ vorhanden ist, während This 0 ist, wenn es nil ist.

Fazit

EFace spielt eine wichtige Rolle bei der Handhabung des any-Typs in der aktuellen Go-Sprache. Es ermöglicht die flexible und schnelle Verarbeitung von Werten verschiedener Typen und den detaillierten Abruf und die Nutzung von Typinformationen. Es ist jedoch zu beachten, dass es sich um eine in der Laufzeitumgebung verborgene Funktion handelt, die von Änderungen der Sprachspezifikation betroffen sein kann.