Any nie je Any, ale Any je Any

Any
V jazyku Go je typ any alias pre interface{}, čarovné rozhranie, ktoré dokáže uchovať hodnoty akéhokoľvek typu. Inými slovami, do premennej typu any možno priradiť hodnotu ľubovoľného typu.
1var a any
2a = 42 // int
3a = "hello" // string
4a = true // bool
5a = 3.14 // float64
Takto typ any umožňuje uchovávať hodnoty rôznych typov, čo umožňuje písať flexibilný kód. Pri používaní typu any však treba dbať na niekoľko vecí.
Type Assertion
Ak chcete použiť skutočnú hodnotu v premennej typu any, musíte použiť type assertion (typovú aserciu). Typová aserícia slúži na to, aby sa kompilátoru oznámilo, že premenná typu any má konkrétny typ.
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}
V uvedenom príklade a.(string) tvrdí, že a je typu string. Ak a nie je typu string, ok bude false a zabráni sa tomu, aby program upadol do panicu.
Type Switch
Type switch (prepínač typov) umožňuje vykonávať rôzne operácie podľa skutočného typu premennej typu any. Je užitočný vtedy, keď treba spracovať viacero typov.
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}
V uvedenom príklade a.(type) kontroluje skutočný typ a a vykoná prípad zodpovedajúci tomuto typu.
Reflection
Balík reflect v jazyku Go umožňuje dynamicky za behu kontrolovať a manipulovať s typom a hodnotou premennej typu any. Je užitočný pri práci so zložitými dátovými štruktúrami.
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())
V uvedenom príklade môžeme pomocou balíka reflect za behu zistiť typ a hodnotu a.
EFace
V jazyku Go sa rozhrania delia na dva hlavné druhy: EFace (Empty Interface) a IFace (Interface with Methods). Z nich EFace zodpovedá pojmu typu any, ktorým sme sa zaoberali doteraz.
Štruktúra
EFace je špeciálna štruktúra, ktorá existuje iba vnútri runtime jazyka Go, preto ju tu budeme spracovávať prostredníctvom mimika (mimic).
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 má dve polia:
Type: ukazovateľ na metadáta obsahujúce informácie o skutočnom type hodnoty.Data: ukazovateľ na samotnú hodnotu.
EFace interne spravuje informácie o type prostredníctvom štruktúry runtimeType. Táto štruktúra obsahuje rôzne metadáta, napríklad veľkosť typu, zarovnanie a hodnotu hash.
Na čo by sme sa v nej mali zamerať, sú polia Kind_, Hash a Equal. Kind_ označuje druh typu, zatiaľ čo Hash a Equal sa používajú na výpočet hodnoty hash a porovnanie typov v hash tabuľkách.
Obalenie
Teraz tento EFace obalíme, aby sme s ním mohli pohodlnejšie pracovať s čiernou mágiou.
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}
Uvedená funkcia prijme premennú typu any, extrahuje z nej vnútornú štruktúru EFace, získa z nej informácie o type a vráti ich ako štruktúru TypeInfo. Teraz môžeme pomocou TypeInfo jednoducho pristupovať k hodnote hash typu, jeho druhu, porovnávacej funkcii a ďalším údajom.
Použitie
Použitie je naozaj jednoduché. Stačí odovzdať premennú typu any funkcii 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}
V uvedenom príklade odovzdávame celé číslo a číslo s pohyblivou desatinnou čiarkou funkcii GetTypeInfo, vypisujeme ich informácie o type a pomocou funkcie Equal porovnávame hodnoty. Vďaka tomu môžeme rýchlejšie získať informácie o type a porovnávať typy.
Tento prístup možno rovnako použiť pre štruktúry, ukazovatele, rozhrania a podobne a zároveň rieši častý problém typovaného nil (typed nil). Hash má hodnotu priradenú vtedy, keď typ existuje, zatiaľ čo This je 0, ak je nil.
Záver
EFace zohráva v súčasnom jazyku Go dôležitú úlohu pri práci s typom any. Vďaka nemu možno hodnoty rôznych typov spracúvať flexibilne a rýchlo a získavať podrobné informácie o type. Keďže však ide o funkciu skrytú v runtime, môže byť ovplyvnená zmenami jazykovej špecifikácie, preto je potrebná opatrnosť.