Any er ikke Any, men Any er Any
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.