GoSuda

Any is niet Any, maar Any is Any

By snowmerak views ...

Any

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

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

Op deze manier stelt het any-type je in staat waarden van uiteenlopende typen op te slaan, wat flexibele code mogelijk maakt. Bij het gebruik van het any-type zijn er echter enkele aandachtspunten.

Type Assertion

Om de daadwerkelijke waarde van een any-variabele te gebruiken, moet je een type assertion (type assertion) toepassen. Een type assertion vertelt de compiler dat een any-variabele een bepaald type heeft.

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 beweert a.(string) dat a van het type string is. Als a niet van het type string is, wordt ok false en kan worden voorkomen dat het programma in paniek raakt.

Type Switch

Een type switch (type switch) stelt je in staat verschillende acties uit te voeren afhankelijk van het daadwerkelijke type van een any-variabele. Dit is nuttig wanneer je 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 daadwerkelijke type van a en voert het case uit dat bij dat type past.

Reflection

Met het reflect-pakket van de Go-taal kun je het type en de waarde van een any-variabele tijdens runtime dynamisch inspecteren en manipuleren. 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 gebruiken we het reflect-pakket om het type en de waarde van a tijdens runtime te controleren.

EFace

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

Structuur

EFace is een speciale struct die alleen binnen de runtime van de Go-taal bestaat, daarom maken we hier een mimic (mimic) om ermee te werken.

 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 de metadata over het daadwerkelijke type van de waarde bevat.
  • Data: een pointer die de daadwerkelijke waarde bevat.

Daarnaast beheert EFace de typeinformatie intern via de runtimeType-struct. Deze struct bevat diverse metadata, zoals de grootte van het type, de uitlijning en de hashwaarde.

Binnen deze struct zijn de velden Kind_, Hash en Equal de delen waar we op moeten letten. Kind_ geeft het soort type aan, terwijl Hash en Equal worden gebruikt om types in hashtabellen te vergelijken en hashwaarden te berekenen.

Verpakking

Laten we nu deze EFace inpakken, zodat we er op een handigere manier zwarte magie mee kunnen bedrijven.

 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}

Deze functie neemt een any-variabele, haalt de interne EFace-struct eruit, extraheert de typeinformatie daaruit en retourneert die als een TypeInfo-struct. Met deze TypeInfo kun je eenvoudig de hashwaarde, het soort en de vergelijkingsfunctie van het type benaderen.

Gebruik

Het gebruik is heel eenvoudig. Je geeft gewoon een any-variabele 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	// 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 geven we een geheel getal en een drijvendekommagetal door aan de functie GetTypeInfo, drukken we de typeinformatie van elk af en vergelijken we de waarden met de functie Equal. Zo kun je sneller typeinformatie verkrijgen en typen vergelijken.

Deze aanpak kan ook op structs, pointers, interfaces en dergelijke worden toegepast, en lost het veelvoorkomende probleem van het getypeerde nil (typed nil) op. Hash is toegewezen zolang er een type is, terwijl This nul is wanneer het nil is.

Conclusie

EFace speelt een belangrijke rol bij het omgaan met het any-type in de Go-taal. Hiermee kunnen waarden van diverse typen flexibel en snel worden verwerkt, en kan gedetailleerde typeinformatie worden verkregen en gebruikt. Let er wel op dat dit een functionaliteit is die verborgen zit in de runtime en daardoor kan worden beïnvloed door wijzigingen in de taalspecificatie.