GoSuda

Any is niet Any, maar Any is Any

By snowmerak
views ...

Any

In de Go-taal is het any-type een alias voor de magische interface, interface{}, die elk type kan bevatten. Dit betekent dat elk type kan worden toegewezen aan een variabele van het any-type.

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

Zoals hierboven getoond, kan het any-type waarden van verschillende typen bevatten, wat het mogelijk maakt om flexibele code te schrijven. Er zijn echter enkele overwegingen bij het gebruik van het any-type.

Type Assertion

Om de daadwerkelijke waarde uit een variabele van het any-type te gebruiken, moet type-assertie worden toegepast. Type-assertie informeert de compiler dat de variabele van het any-type een specifiek type is.

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 het bovenstaande voorbeeld stelt a.(string) dat a van het type string is. Als a niet van het type string is, wordt ok false, waardoor wordt voorkomen dat het programma in paniek raakt.

Type Switch

Een type switch maakt het mogelijk om verschillende bewerkingen uit te voeren op basis van het daadwerkelijke type van een variabele van het any-type. Dit is nuttig wanneer meerdere typen moeten worden verwerkt.

 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 het bovenstaande voorbeeld controleert a.(type) het daadwerkelijke type van a en voert de overeenkomstige case uit.

Reflection

Door gebruik te maken van het reflect-pakket in de Go-taal kunnen het type en de waarde van een variabele van het any-type dynamisch worden geïnspecteerd en gemanipuleerd tijdens runtime. Dit is nuttig bij het werken met complexe datastructuren.

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 het bovenstaande voorbeeld kunnen het type en de waarde van a tijdens runtime worden geverifieerd met behulp van het reflect-pakket.

EFace

In de Go-taal zijn interfaces onderverdeeld in twee hoofdvormen: EFace (Empty Interface) en IFace (Interface with Methods). Onder deze is EFace hetzelfde concept als het any-type dat we tot nu toe hebben besproken.

Structuur

EFace is een speciale structuur die alleen binnen de Go-runtime bestaat; daarom zullen we hier een mimic maken om deze te bespreken.

 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}

EFace heeft twee velden:

  • Type: Een pointer die metadata bevat over het daadwerkelijke type van de waarde.
  • Data: Een pointer die de daadwerkelijke waarde bevat.

Bovendien beheert EFace intern type-informatie via de runtimeType-structuur. Deze structuur bevat diverse metadata, zoals de grootte, uitlijning en hash-waarde van het type.

De velden die onze aandacht verdienen, zijn Kind_, Hash en Equal. Kind_ geeft het type aan, terwijl Hash en Equal worden gebruikt voor het vergelijken van typen en het berekenen van hash-waarden in hash-tabellen.

Verpakking

Laten we nu deze EFace wrappen om gemakkelijker "zwarte magie" toe te passen.

 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}

De bovenstaande functie accepteert een variabele van het any-type, extraheert de interne EFace-structuur en haalt daaruit de type-informatie op, die wordt geretourneerd als een TypeInfo-structuur. Nu kunnen we eenvoudig toegang krijgen tot de hash-waarde, het type en de vergelijkingsfunctie van het type met behulp van deze TypeInfo.

Toepassing

De toepassing is zeer eenvoudig. Geef de variabele van het any-type gewoon door aan de functie 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	// Vergelijk de twee waarden met behulp van de Equal-functie uit de type-informatie
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 het bovenstaande voorbeeld worden een integer en een floating-point waarde doorgegeven aan de functie GetTypeInfo om hun respectievelijke type-informatie af te drukken, en de functie Equal wordt gebruikt om de waarden te vergelijken. Dit maakt het mogelijk om sneller type-informatie te verkrijgen en typen te vergelijken.

Bovendien kan deze methode op dezelfde manier worden toegepast op structuren, pointers en interfaces, en kan het ook het niet onbelangrijke probleem van "typed nil" oplossen. Hoewel Hash wordt toegewezen als er een type is, is This 0 als het nil is.

Conclusie

EFace speelt momenteel een cruciale rol bij het omgaan met het any-type in de Go-taal. Dit maakt het mogelijk om waarden van verschillende typen flexibel en snel te verwerken, en om gedetailleerde type-informatie te verkrijgen en te gebruiken. Het is echter belangrijk op te merken dat dit een verborgen functie binnen de runtime is en onderhevig kan zijn aan wijzigingen in de taalspecificatie.