Mikä tahansa ei ole Mikä tahansa, mutta Mikä tahansa on Mikä tahansa
Any
Go-kielessä any
-tyyppi on maagisen rajapinnan, interface{}
:n, aliaskonstruktio, joka kykenee sisältämään minkä tahansa tyypin. Toisin sanoen, mikä tahansa tyyppi voidaan sijoittaa any
-tyyppiseen muuttujaan.
1var a any
2a = 42 // kokonaisluku
3a = "hello" // merkkijono
4a = true // totuusarvo
5a = 3.14 // liukuluku
Kuten nähtävissä, any
-tyyppi mahdollistaa joustavan koodin luomisen, sillä se voi sisältää erilaisten tyyppien arvoja. Kuitenkin any
-tyypin käytössä on huomioitava useita seikkoja.
Type Assertion
Jotta any
-tyyppisen muuttujan todellista arvoa voidaan käyttää, on hyödynnettävä tyyppivarmistusta (type assertion). Tyyppivarmistus toimii ilmoituksena kääntäjälle siitä, että any
-tyyppinen muuttuja edustaa tiettyä tyyppiä.
1var a any = "hello"
2str, ok := a.(string)
3if ok {
4 fmt.Println("Merkkijonoarvo:", str)
5} else {
6 fmt.Println("Ei ole merkkijono")
7}
Edellä olevassa esimerkissään lauseke a.(string)
varmistaa, että a
on tyyppiä string
. Mikäli a
ei ole tyyppiä string
, muuttujan ok
arvoksi tulee false
, mikä estää ohjelman kaatumisen (panic).
Type Switch
Tyyppikytkin (type switch) mahdollistaa erilaisten toimintojen suorittamisen any
-tyyppisen muuttujan todellisen tyypin perusteella. Tämä on hyödyllistä tilanteissa, joissa täytyy käsitellä useita eri tyyppejä.
1var a any = 42
2switch v := a.(type) {
3case int:
4 fmt.Println("Kokonaisluku:", v)
5case string:
6 fmt.Println("Merkkijono:", v)
7case bool:
8 fmt.Println("Totuusarvo:", v)
9default:
10 fmt.Println("Tuntematon tyyppi")
11}
Edellä olevassa esimerkissään lauseke a.(type)
tarkistaa muuttujan a
todellisen tyypin ja suorittaa vastaavan case
-lohkon.
Reflection
Go-kielen reflect
-paketin avulla voidaan dynaamisesti tarkastella ja manipuloida any
-tyyppisen muuttujan tyyppiä ja arvoa ajonaikaisesti (runtime). Tämä on hyödyllistä käsiteltäessä monimutkaisia tietorakenteita.
1import (
2 "fmt"
3 "reflect"
4)
5
6var a any = 3.14
7v := reflect.ValueOf(a)
8fmt.Println("Tyyppi:", v.Type())
9fmt.Println("Arvo:", v.Float())
Edellä olevassa esimerkissään reflect
-paketin avulla voidaan ajonaikaisesti todentaa muuttujan a
tyyppi ja arvo.
EFace
Go-kielessä rajapinnat jakautuvat kahteen päämuotoon: EFace (Empty Interface) ja IFace (Interface with Methods). Näistä EFace on sama käsite kuin tähän mennessä käsittelemämme any
-tyyppi.
구조
Koska EFace on erityinen rakenteellinen tyyppi, joka on olemassa ainoastaan Go-kielen ajonaikaisessa ympäristössä (runtime), käsittelemme sitä luomalla sille jäljitelmän (mimic).
1type runtimeTypeMimic struct {
2 Size_ uintptr
3 PtrBytes uintptr // osoittimia sisältävien etuliitebyttien lukumäärä
4 Hash uint32 // tyypin tiiviste; välttää laskentaa hajautustauluissa
5 TFlag uint8 // tyypin lisätietoliput
6 Align_ uint8 // tämän tyypin muuttujan kohdistus
7 FieldAlign_ uint8 // struct-kentän kohdistus tämän tyypin mukaan
8 Kind_ uint8 // enumeeraatio C:lle
9 Equal func(unsafe.Pointer, unsafe.Pointer) bool
10 GCData *byte
11 Str int32 // merkkimuoto
12 PtrToThis int32 // osoitin tämän tyypin tyyppiin, voi olla nolla
13}
14
15type eFaceMimic struct {
16 Type *runtimeTypeMimic
17 Data unsafe.Pointer
18}
EFace sisältää kaksi kenttää:
Type
: Osoitin, joka sisältää metatietoa arvon todellisesta tyypistä.Data
: Osoitin, joka sisältää itse arvon.
Lisäksi EFace hallinnoi tyyppitietoja sisäisesti runtimeType
-rakenteen kautta. Tämä rakenne sisältää monenlaisia metatietoja, kuten tyypin koon, kohdistuksen ja tiivisteen arvon.
Tässä rakenteessa meidän tulisi kiinnittää huomiota kenttiin Kind_
, Hash
ja Equal
. Kind_
ilmoittaa tyypin luokan, ja kenttiä Hash
sekä Equal
käytetään tyypin vertailuun hajautustauluissa ja tiivisteen arvon laskemiseen.
포장
Luodaan nyt tämä EFace kääreeseen, jotta voimme suorittaa tähän liittyvää syvempää manipulaatiota helpommin.
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 vastaanottaa any
-tyyppisen muuttujan, poimii siitä sisäisen EFace-rakenteen ja palauttaa siitä erotellut tyyppitiedot TypeInfo
-rakenteeseen. Tämän TypeInfo
-rakenteen avulla voimme nyt helposti käyttää tyypin tiivisteen arvoa, luokkaa ja vertailufunktiota.
활용
Käyttö on erittäin yksinkertaista; GetTypeInfo
-funktiolle tulee ainoastaan välittää any
-tyyppinen muuttuja.
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 // Vertaa kahta arvoa käyttämällä tyyppitiedon Equal-funktiota
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}
Edellisessä esimerkissä kokonaisluku ja liukuluku välitetään GetTypeInfo
-funktiolle, jolloin kunkin tyyppitiedot tulostetaan ja arvoja verrataan käyttäen Equal
-funktiota. Tämän menetelmän avulla tyyppitiedon saaminen ja tyyppien vertailu on mahdollista suorittaa nopeammin.
Lisäksi tätä lähestymistapaa voidaan soveltaa samalla tavalla rakenteisiin, osoittimiin ja rajapintoihin, ja se kykenee ratkaisemaan myös usein ilmenevän ongelman, nimittäin tyypillisen nil-arvon (typed nil) tapauksen. Tämä johtuu siitä, että Hash
on todennäköisesti määritetty, jos tyyppi on olemassa, kun taas This
saa arvon nolla, jos arvo on nil.
결론
EFace näyttelee merkittävää roolia käsiteltäessä any
-tyyppiä nykyisessä Go-kielessä. Sen avulla mahdollistuu erilaisten tyyppien arvojen joustava ja nopea käsittely sekä tyyppitietojen yksityiskohtainen hankinta ja hyödyntäminen. On kuitenkin syytä huomioida, että tämä on ajonaikaisen ympäristön sisällä piilotettu toiminnallisuus, joka voi altistua kielen spesifikaation muutosten vaikutuksille.