GoSuda

Any er ikke Any, men Any er Any

By snowmerak views ...

Any

I Go er any-typen et alias for interface{}, en magisk grænseflade, der kan rumme alle typer. Det betyder, at en værdi af hvilken som helst type kan tildeles en any-variabel.

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

Således gør any-typen det muligt at skrive fleksibel kode, da den kan indeholde værdier af forskellige typer. Ved brug af any-typen bør man dog være opmærksom på nogle forhold.

Type Assertion

For at anvende den faktiske værdi i en any-variabel skal man bruge en typeassertion (type assertion). Typeassertionen fortæller compileren, at any-variablen har en bestemt type.

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}

I eksemplet ovenfor fastslår a.(string), at a er af typen string. Hvis a ikke er af typen string, bliver ok false, og programmet undgår at gå i panik.

Type Switch

En type switch (type switch) gør det muligt at udføre forskellige handlinger afhængigt af den faktiske type i en any-variabel. Dette er nyttigt, når flere typer skal håndteres.

 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}

I eksemplet ovenfor undersøger a.(type) den faktiske type af a og udfører den case, der svarer til denne type.

Reflection

Ved hjælp af pakken reflect i Go kan man dynamisk undersøge og manipulere typen og værdien af en any-variabel under kørsel. Dette er nyttigt, når man arbejder med komplekse datastrukturer.

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

I eksemplet ovenfor bruges pakken reflect til at bestemme typen og værdien af a under kørsel.

EFace

I Go deles grænseflader op i to hovedformer: EFace (Empty Interface) og IFace (Interface with Methods). Af disse er EFace det samme koncept som any-typen, som vi hidtil har behandlet.

Struktur

EFace er en speciel struktur, der kun findes inde i Go's runtime, og derfor vil vi her arbejde med en mimic (mimic) til at efterligne den.

 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 har to felter:

  • Type: En pointer, der indeholder metadata om værdiens faktiske type.
  • Data: En pointer, der indeholder selve værdien.

EFace administrerer desuden typeinformation internt gennem strukturen runtimeType. Denne struktur indeholder forskellige metadata, såsom typens størrelse, justering og hashværdi.

De felter, vi bør være særligt opmærksomme på, er Kind_, Hash og Equal. Kind_ angiver typens art, mens Hash og Equal bruges til at beregne hashværdier og sammenligne typer i hashtabeller.

Indpakning

Lad os nu indpakke denne EFace, så vi kan anvende den sorte magi mere bekvemt.

 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}

Ovenstående funktion modtager en any-variabel, udtrækker den interne EFace-struktur, henter typeinformationen herfra og returnerer den som en TypeInfo-struktur. Med denne TypeInfo kan man nu nemt tilgå typens hashværdi, art, sammenligningsfunktion m.m.

Anvendelse

Anvendelsen er meget enkel. Man sender blot en any-variabel til funktionen 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}

I eksemplet ovenfor sendes en heltalsværdi og en flydende kommaværdi til GetTypeInfo, hvorefter typeinformationen for hver af dem udskrives, og værdierne sammenlignes ved hjælp af funktionen Equal. På den måde kan man hurtigere få typeinformation og sammenligne typer.

Denne metode kan desuden anvendes på structs, pointere, grænseflader m.m., og den kan også løse det hyppigt forekommende problem med typed nil. Hash er tildelt, hvis der findes en type, mens This er 0, hvis værdien er nil.

Konklusion

EFace spiller i dag en vigtig rolle, når any-typen skal håndteres i Go. Det gør det muligt at behandle værdier af forskellige typer fleksibelt og effektivt, og typeinformation kan hentes og anvendes i detaljer. Det skal dog bemærkes, at dette er en skjult funktionalitet inde i runtime, som kan blive påvirket af ændringer i sprogspecifikationen, og derfor bør man udvise forsigtighed.