Any ei ole Any, mutta Any on Any

Any
Go-kielessä any-tyyppi on interface{}:n alias, eli maaginen rajapinta, joka voi sisältää minkä tahansa tyypin. Toisin sanoen mikä tahansa tyyppi voidaan sijoittaa any-tyyppiseen muuttujaan.
1var a any
2a = 42 // int
3a = "hello" // string
4a = true // bool
5a = 3.14 // float64
Näin any-tyyppi voi sisältää eri tyyppisiä arvoja, mikä mahdollistaa joustavan koodin kirjoittamisen. Any-tyypin käytössä on kuitenkin muutamia huomioitavia seikkoja.
Type Assertion
Jotta any-tyyppisen muuttujan todellista arvoa voidaan käyttää, on käytettävä tyyppiassertiota (type assertion). Tyyppiassertio kertoo kääntäjälle, että any-tyyppinen muuttuja on tietyn tyyppinen.
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}
Yllä olevassa esimerkissä a.(string) väittää, että a on string-tyyppinen. Jos a ei ole string-tyyppinen, ok saa arvon false, ja ohjelman paniikkitila voidaan estää.
Type Switch
Tyyppikytkin (type switch) mahdollistaa eri toiminnot suorittamisen any-tyyppisen muuttujan todellisen tyypin mukaan. Tämä on hyödyllistä, kun on käsiteltävä useita tyyppejä.
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}
Yllä olevassa esimerkissä a.(type) tarkistaa a:n todellisen tyypin ja suorittaa vastaavan haaran.
Reflection
Go-kielen reflect-paketin avulla any-tyyppisen muuttujan tyyppiä ja arvoa voidaan tarkistaa ja muokata dynaamisesti ajonaikana. Tämä on hyödyllistä, kun käsitellään monimutkaisia tietorakenteita.
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())
Yllä olevassa esimerkissä reflect-paketin avulla voidaan tarkistaa a:n tyyppi ja arvo ajonaikana.
EFace
Go-kielessä rajapinnat jakautuvat kahteen päämuotoon: EFace (Empty Interface) ja IFace (Interface with Methods). Näistä EFace vastaa samaa käsitettä kuin tähän asti käsittelemämme any-tyyppi.
Rakenne
EFace on erityinen rakenne, joka on olemassa vain Go-kielen ajonaikaisessa ympäristössä, joten tässä käsittelemme sitä luomalla sen mimicin (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 koostuu kahdesta kentästä:
Type: osoitin, joka sisältää arvon todellisen tyypin metatiedot.Data: osoitin, joka sisältää itse arvon.
Lisäksi EFace hallinnoi tyyppitietoja sisäisesti runtimeType-rakenteen kautta. Tämä rakenne sisältää tyypin koon, tasauksen, hash-arvon ja muita metatietoja.
Tässä kohdassa huomion arvoisia ovat kentät Kind_, Hash ja Equal. Kind_ ilmaisee tyypin lajin, ja Hash sekä Equal käytetään tyyppien vertailuun ja hash-arvon laskemiseen hash-taulukoissa.
Kääre
Kääritään nyt tämä EFace, jotta mustan magian käyttö on helpompaa.
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}
Yllä oleva funktio ottaa vastaan any-tyyppisen muuttujan, poimii sen sisäisen EFace-rakenteen, hakee siitä tyyppitiedot ja palauttaa ne TypeInfo-rakenteena. Tämän TypeInfo:n avulla tyypin hash-arvoon, lajiin, vertailufunktioon ym. pääsee helposti käsiksi.
Käyttö
Käyttö on todella helppoa. Välitä vain any-tyyppinen muuttuja funktiolle 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}
Yllä olevassa esimerkissä kokonaisluku ja liukuluku välitetään funktiolle GetTypeInfo, jolloin kummankin tyyppitiedot tulostetaan. Lisäksi Equal-funktiolla verrataan arvoja keskenään. Näin tyyppitiedot saadaan nopeammin ja tyyppejä voidaan verrata.
Tämä tapa toimii samalla tavalla myös rakenteille, osoittimille, rajapinnoille ym., ja sillä voidaan ratkaista myös melko yleinen ongelma, typed nil -ongelma. Hash on asetettu aina, kun tyyppi on olemassa, kun taas This on nolla, jos arvo on nil.
Johtopäätös
EFace on nykyisin keskeisessä roolissa käsiteltäessä any-tyyppiä Go-kielessä. Sen avulla eri tyyppisiä arvoja voidaan käsitellä joustavasti ja nopeasti, ja tyyppitietoja voidaan saada ja hyödyntää yksityiskohtaisesti. Tämä on kuitenkin ajonaikaan piilotettu toiminnallisuus, joka saattaa muuttua kielimäärittelyn muutosten myötä, joten siihen on suhtauduttava varovaisesti.