Az Any nem Any, de az Any Any.

Any
A Go nyelvben az any típus a interface{} alias-a, egy varázslatos interfész, amely bármilyen típusú értéket képes tárolni. Ez azt jelenti, hogy bármilyen típusú értéket hozzárendelhetünk egy any típusú változóhoz.
1var a any
2a = 42 // int
3a = "hello" // string
4a = true // bool
5a = 3.14 // float64
Ahogy ez a példa is mutatja, az any típus különböző típusú értékek tárolására alkalmas, ezáltal rugalmas kódot írhatunk. Az any típus használatakor azonban néhány dologra figyelni kell.
Type Assertion
Ha az any típusú változóból szeretnénk a tényleges értéket használni, típusállításra (type assertion) van szükség. A típusállítás arra szolgál, hogy tájékoztassa a fordítót arról, hogy az any típusú változó egy adott típusú.
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}
A fenti példában az a.(string) kifejezés azt állítja, hogy a string típusú. Ha a nem string típusú, akkor ok értéke false lesz, és így megakadályozható, hogy a program pánikba essen.
Type Switch
A típuskapcsoló (type switch) lehetővé teszi, hogy az any típusú változó tényleges típusától függően különböző műveleteket hajtsunk végre. Ez különösen hasznos, ha több típust kell kezelnünk.
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}
A fenti példában az a.(type) megvizsgálja a tényleges típusát, és végrehajtja az adott típusnak megfelelő ágat.
Reflection
A Go nyelv reflect csomagjának használatával az any típusú változók típusát és értékét futásidőben dinamikusan vizsgálhatjuk és manipulálhatjuk. Ez összetett adatstruktúrák kezelésekor hasznos.
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())
A fenti példában a reflect csomag segítségével futásidőben ellenőrizhetjük a típusát és értékét.
EFace
A Go nyelvben az interfészek két fő formába sorolhatók: EFace (Empty Interface) és IFace (Interface with Methods). Ezek közül az EFace felel meg a mostanáig tárgyalt any típus fogalmának.
Szerkezet
Az EFace a Go nyelv futtatókörnyezetén belül létező speciális struktúra, ezért itt egy mimikust (mimic) hozunk létre a kezelésére.
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}
Az EFace két mezővel rendelkezik:
Type: a érték tényleges típusára vonatkozó metaadatokat tartalmazó mutató.Data: a tényleges értéket tartalmazó mutató.
Az EFace belsőleg a runtimeType struktúrán keresztül kezeli a típusinformációkat. Ez a struktúra a típus méretét, igazítását, hash értékét és további metaadatait tartalmazza.
Ebben a struktúrában a Kind_, Hash és Equal mezők érdemelnek figyelmet. A Kind_ a típus fajtáját jelöli, a Hash és Equal pedig a hash táblákban történő típusösszehasonlításhoz és hash értékek számításához használhatók.
Csomagolás
Most csomagoljuk be ezt az EFace-t, hogy kényelmesebben lehessen fekete mágiát alkalmazni.
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}
A fenti függvény egy any típusú változót fogad, kinyeri belőle a belső EFace struktúrát, majd abból kiveszi a típusinformációkat, és TypeInfo struktúraként adja vissza. Ezt a TypeInfo-t használva könnyen elérhetjük a típus hash értékét, fajtáját, összehasonlító függvényét stb.
Használat
A használat nagyon egyszerű: egyszerűen át kell adnunk egy any típusú változót a GetTypeInfo függvénynek.
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}
A fenti példában az egész számot és a lebegőpontos értéket átadjuk a GetTypeInfo függvénynek, kiírjuk a típusinformációkat, majd az Equal függvénnyel összehasonlítjuk az értékeket. Így gyorsabban juthatunk típusinformációhoz, és összehasonlíthatjuk a típusokat.
Ez a módszer emellett struktúrákra, mutatókra, interfészekre is ugyanúgy alkalmazható, és megoldja a gyakran felmerülő típusos nil (typed nil) problémát is, hiszen a Hash típus megléte esetén mindig be van állítva, míg a This nil értéke 0.
Következtetés
Az EFace jelenleg is fontos szerepet játszik abban, hogy a Go nyelvben kezeljük az any típust. Segítségével különböző típusú értékeket rugalmasan és gyorsan dolgozhatunk fel, és részletes típusinformációkat szerezhetünk és használhatunk. Ugyanakkor ez a futtatókörnyezeten belül rejtett funkcionalitás, amely a nyelvi specifikáció változásainak hatása alá eshet, ezért óvatosan kell vele bánni.