Any er ikke Any, men Any er Any

Any
I Go-språket er any-typen et alias for interface{}, et magisk grensesnitt som kan inneholde alle typer. Det betyr at en variabel av typen any kan tilordnes en verdi av hvilken som helst type.
1var a any
2a = 42 // int
3a = "hello" // string
4a = true // bool
5a = 3.14 // float64
Slik sett gjør any-typen det mulig å lagre verdier av ulike typer, noe som gir rom for fleksibel kode. Ved bruk av any-typen må man imidlertid være oppmerksom på noen forhold.
Type Assertion
For å bruke den faktiske verdien i en variabel av typen any må man bruke typeassertion (type assertion). Typeassertion gir kompilatoren beskjed om at en any-variabel 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 eksempelet over fastslår a.(string) at a er av typen string. Dersom a ikke er av typen string, blir ok lik false, og man kan dermed unngå at programmet får panikk.
Type Switch
Typebryter (type switch) gjør det mulig å utføre ulike handlinger avhengig av den faktiske typen til en any-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 eksempelet over undersøker a.(type) den faktiske typen til a og utfører den casen som samsvarer med denne typen.
Reflection
Ved hjelp av pakken reflect i Go kan man dynamisk undersøke og manipulere typen og verdien til en any-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 eksempelet over brukes pakken reflect til å kontrollere typen og verdien til a under kjøretid.
EFace
I Go-språket deles grensesnitt 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 bare finnes inne i kjøretidsmiljøet (runtime) i Go, og derfor skal vi arbeide med en mimic (mimic) for å håndtere den her.
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 den faktiske typen til verdien.Data: En peker som inneholder selve verdien.
I tillegg håndterer EFace typeinformasjon internt gjennom strukturen runtimeType. Denne strukturen inneholder ulike metadata, blant annet størrelse, justering og hashverdi for typen.
Det er særlig feltene Kind_, Hash og Equal vi bør merke oss. Kind_ angir hvilken kategori typen tilhører, mens Hash og Equal brukes til å beregne hashverdier og sammenligne typer i hashtabeller.
Innpakning
La oss nå pakke inn denne EFace-strukturen, slik at vi enklere kan utnytte den lavnivåmagien.
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 over tar imot en variabel av typen any, henter ut den interne EFace-strukturen og returnerer typeinformasjonen i form av en TypeInfo-struktur. Med TypeInfo kan man deretter enkelt få tilgang til typens hashverdi, kategori, sammenligningsfunksjon og så videre.
Bruk
Bruken er svært enkel. Man sender ganske enkelt en variabel av typen any til funksjonen 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 eksempelet over sendes et heltall og et flyttall til GetTypeInfo, typeinformasjonen for hver av dem skrives ut, og funksjonen Equal brukes til å sammenligne verdiene. På denne måten kan man raskere hente ut typeinformasjon og sammenligne typer.
Metoden kan dessuten brukes på strukturer, pekere, grensesnitt og lignende, og den kan også løse det ikke ubetydelige problemet med typed nil (typed nil). Hash er tildelt så lenge typen finnes, mens This er 0 dersom verdien er nil, og dette gjør det mulig å skille tilfellene fra hverandre.
Konklusjon
EFace spiller en viktig rolle i håndteringen av any-typen i Go-språket i dag. Det gir fleksibel og rask behandling av verdier av ulike typer, og gjør det mulig å hente ut og utnytte detaljert typeinformasjon. Dette er imidlertid en funksjonalitet som ligger skjult i kjøretidsmiljøet, og den kan påvirkes av endringer i språkspesifikasjonen, så man bør være forsiktig med bruken.