GoSuda

Any is niet Any, maar Any is Any

By snowmerak
views ...

Any

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

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

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

Type Assertion

Om de werkelijke waarde van een 'any'-typevariabele te gebruiken, moet u type assertion toepassen. Type assertion informeert de compiler dat de 'any'-typevariabele van 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) vast dat a van het type string is. Als a niet van het type string is, wordt ok false, wat voorkomt dat het programma in paniek raakt.

Type Switch

Een type switch stelt u in staat om verschillende acties uit te voeren afhankelijk van het werkelijke type van een 'any'-typevariabele. Dit is nuttig wanneer u meerdere typen moet verwerken.

 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 werkelijke type van a en voert de overeenkomstige case uit.

Reflection

Door het reflect-pakket van de Go-taal te gebruiken, kunt u het type en de waarde van een 'any'-typevariabele dynamisch inspecteren en manipuleren tijdens runtime. Dit is gunstig bij het omgaan 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 conceptueel identiek aan het 'any'-type dat we tot nu toe hebben besproken.

Structuur

Aangezien EFace een speciale structuur is die alleen binnen de runtime van de Go-taal bestaat, zullen we hier een nabootsing (mimic) creëren om deze te behandelen.

 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 omvat twee velden:

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

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

De velden waarop wij ons moeten richten zijn Kind_, Hash en Equal. Kind_ duidt het type aan, terwijl Hash en Equal worden gebruikt voor het vergelijken van typen en het berekenen van hash-waarden in hashtabellen.

Verpakking

Laten we nu deze EFace inpakken om het toepassen van "zwarte magie" gemakkelijker te maken.

 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 'any'-typevariabele, extraheert de interne EFace-structuur en haalt daaruit de type-informatie op, die het retourneert als een TypeInfo-structuur. Met deze TypeInfo is het nu eenvoudig om toegang te krijgen tot de hash-waarde, het soort en de vergelijkingsfunctie van het type.

Toepassing

De toepassing is zeer eenvoudig. U hoeft alleen de 'any'-typevariabele door te geven aan de GetTypeInfo-functie.

 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}

In het bovenstaande voorbeeld worden een integer en een floating-point waarde doorgegeven aan de GetTypeInfo-functie om hun respectievelijke type-informatie af te drukken en worden de waarden vergeleken met behulp van de Equal-functie. Dit maakt het mogelijk om sneller type-informatie te verkrijgen en typen te vergelijken.

Bovendien kan deze methode op dezelfde manier worden toegepast op structs, pointers en interfaces, en kan het veelvoorkomende probleem van typed nil (getypeerde nul) worden opgelost. Hoewel Hash zal worden toegewezen als er een type is, zal This nul zijn als het nil is.

Conclusie

EFace speelt momenteel een cruciale rol bij het omgaan met het 'any'-type in de Go-taal. Het maakt een flexibele en snelle verwerking van waarden van diverse typen mogelijk en biedt gedetailleerde toegang tot type-informatie. Er moet echter worden opgemerkt dat dit een verborgen functionaliteit binnen de runtime is en vatbaar kan zijn voor wijzigingen in de taalspecificatie.