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. Dette betyr at enhver type kan tilordnes en variabel av typen any.

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

Som vist her, kan any-typen inneholde verdier av ulike typer, noe som muliggjør fleksibel kodeskriving. Imidlertid er det noen forholdsregler å ta når man bruker any-typen.

Type Assertion

For å bruke den faktiske verdien fra en any-type variabel, må type assertion brukes. Type assertion informerer kompilatoren om at any-type variabelen 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, vil ok være false, og dette forhindrer at programmet panikker.

Type Switch

Type switch gjør det mulig å utføre forskjellige handlinger basert på den faktiske typen til en any-type variabel. Dette er nyttig når flere typer må 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 kontrollerer a.(type) den faktiske typen til a og utfører den tilsvarende casen.

Reflection

Ved å bruke Go-språkets reflect-pakke kan man dynamisk inspisere og manipulere typen og verdien til en any-type variabel ved 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 å bekrefte typen og verdien til a ved kjøretid.

EFace

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

Struktur

EFace er en spesiell struktur som kun eksisterer i Go-språkets runtime, så her vil vi lage en etterligning (mimic) for å diskutere det.

 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 består av to felt:

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

EFace administrerer internt typeinformasjon via runtimeType-strukturen. Denne strukturen inkluderer ulike metadata som typens størrelse, justering og hashverdi.

De delene vi bør fokusere på her er feltene Kind_, Hash og Equal. Kind_ representerer typen av en type, mens Hash og Equal brukes til å sammenligne typer og beregne hashverdier i hashtabeller.

Pakking

La oss nå pakke 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 typeinformasjonen fra den, og returnerer den som en TypeInfo-struktur. Nå kan vi enkelt få tilgang til typens hashverdi, type, sammenligningsfunksjon og mer ved å bruke denne TypeInfo.

Anvendelse

Anvendelsen er svært enkel. Man trenger bare å sende 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, eface2.This))
15}

I eksemplet ovenfor sendes en heltallsverdi og en flyttallsverdi 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 løse problemet med "typed nil" som oppstår ganske ofte. Hash vil være tildelt hvis typen finnes, men This vil være 0 hvis den er nil.

Konklusjon

EFace spiller for tiden en viktig rolle i håndteringen av any-typen i Go-språket. Gjennom dette kan man behandle verdier av ulike typer fleksibelt og raskt, samt få detaljert typeinformasjon og utnytte den. Det er imidlertid viktig å merke seg at dette er en funksjon som er skjult i runtime, og den kan bli påvirket av endringer i språkspesifikasjonene.