GoSuda

Any er ikke Any, men Any er Any

By snowmerak
views ...

Any

I Go-språket er any-typen et alias for det magiske grensesnittet interface{}, som kan inneholde alle typer. Det vil si at enhver type kan tilordnes en any-type variabel.

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

Som vist kan any-typen inneholde verdier av forskjellige typer, noe som muliggjør skriving av fleksibel kode. Det er imidlertid noen punkter man bør merke seg når man bruker any-typen.

Type Assertion

For å bruke den faktiske verdien fra en any-type variabel, må man bruke type assertion. Type assertion informerer kompilatoren om at en any-type variabel er av en spesifikk 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 bekrefter a.(string) at a er av typen string. Hvis a ikke er av typen string, blir ok false, og dette forhindrer at programmet panikker.

Type Switch

Type switch gjør det mulig å utføre forskjellige handlinger basert på den faktiske typen av en any-type variabel. Dette er nyttig når man må håndtere flere typer.

 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 sjekker a.(type) den faktiske typen av a og utfører den tilsvarende casen.

Reflection

Ved å bruke Go-språkets reflect-pakke kan man dynamisk inspisere og manipulere typen og verdien av en any-type variabel under kjøretid. Dette er nyttig når man arbeider 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 kan reflect-pakken brukes til å sjekke typen og verdien av a under kjøretid.

EFace

I Go-språket er grensesnitt delt inn i to hovedformer: EFace (Empty Interface) og IFace (Interface with Methods). Av disse er EFace det samme konseptet som any-typen vi har behandlet så langt.

Struktur

EFace er en spesiell struktur som kun eksisterer innenfor Go-språkets runtime, så vi vil lage en etterligning (mimic) her for å behandle 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 felt:

  • Type: En peker som inneholder metadata om verdiens faktiske type.
  • Data: En peker som inneholder den faktiske verdien.

Og EFace administrerer internt typeinformasjon gjennom runtimeType-strukturen. Denne strukturen inneholder ulike metadata som typens størrelse, justering, hash-verdi, etc.

Delene vi bør fokusere på her er feltene Kind_, Hash og Equal. Kind_ indikerer typen, mens Hash og Equal brukes til å sammenligne typer og beregne hash-verdier i hashtabeller.

Innpakning

La oss nå pakke inn denne EFace for å gjøre det enklere å utføre "svart magi".

 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}

Funksjonen ovenfor mottar en any-type variabel, trekker ut den interne EFace-strukturen, og henter ut typeinformasjon derfra for å returnere den som en TypeInfo-struktur. Nå kan denne TypeInfo brukes til enkelt å aksessere typens hash-verdi, type, sammenligningsfunksjon, osv.

Anvendelse

Anvendelsen er virkelig enkel. Man sender bare en any-type variabel til GetTypeInfo-funksjonen.

 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 blir heltall- og flyttallverdier sendt til GetTypeInfo-funksjonen for å skrive ut deres respektive typeinformasjon, og Equal-funksjonen brukes til å sammenligne verdiene. Dette gjør det mulig å få typeinformasjon og sammenligne typer raskere.

Denne metoden kan også brukes på strukturer, pekere og grensesnitt, og den kan også løse problemet med "typed nil" som oppstår ganske ofte. Hash vil være tildelt hvis det er en type, men This vil være 0 hvis det er nil.

Konklusjon

EFace spiller for tiden en viktig rolle i håndteringen av any-typen i Go-språket. Gjennom dette kan ulike typer verdier behandles fleksibelt og raskt, og typeinformasjon kan hentes ut og brukes i detalj. Det skal imidlertid bemerkes at dette er en funksjon skjult i runtime og kan bli påvirket av endringer i språkespesifikasjonen.