GoSuda

Any ei ole Any, mutta Any on Any

By snowmerak
views ...

Any

Go-kielessä any-tyyppi on maagisen interface{}-rajapinnan alias, 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

Kuten edellä esitettiin, any-tyyppi voi sisältää erilaisten tyyppien arvoja, mikä mahdollistaa joustavan koodin kirjoittamisen. any-tyypin käytössä on kuitenkin joitakin huomioitavia seikkoja.

Type Assertion

Jotta any-tyyppisen muuttujan todellista arvoa voidaan käyttää, on käytettävä tyyppiväittämää (type assertion). Tyyppiväittämä kertoo kääntäjälle, että any-tyyppinen muuttuja on tiettyä tyyppiä.

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 on false, ja ohjelman paniikin voi estää.

Type Switch

Tyyppikytkin (type switch) mahdollistaa any-tyyppisen muuttujan todellisen tyypin mukaan erilaisen toiminnan. 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 tapauksen.

Reflection

Go-kielen reflect-paketin avulla any-tyyppisen muuttujan tyyppiä ja arvoa voidaan tarkastella ja manipuloida dynaamisesti suoritusajassa. 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("Type:", v.Type())
9fmt.Println("Value:", v.Float())

Yllä olevassa esimerkissä reflect-pakettia käytetään a:n tyypin ja arvon tarkistamiseen suoritusajassa.

EFace

Go-kielessä rajapinnat jaetaan kahteen päämuotoon: EFace (Empty Interface) ja IFace (Interface with Methods). Näistä EFace on sama käsite kuin tähän asti käsitelty any-tyyppi.

Rakenne

EFace on erityinen rakenne, joka on olemassa vain Go-kielen suoritusajassa, joten tässä käsittelemme sitä jäljitelmänä (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 sisältää kaksi kenttää:

  • Type: Osoitin, joka sisältää metatiedot arvon todellisesta tyypistä.
  • Data: Osoitin, joka sisältää todellisen arvon.

EFace hallitsee sisäisesti tyyppitietoja runtimeType-rakenteen kautta. Tämä rakenne sisältää erilaisia metatietoja, kuten tyypin koon, kohdistuksen ja hajautusarvon.

Tässä meidän on kiinnitettävä huomiota Kind_-, Hash- ja Equal-kenttiin. Kind_ osoittaa tyypin lajin, kun taas Hash ja Equal -kenttiä käytetään tyyppien vertailuun ja hajautusarvojen laskemiseen hajautustauluissa.

Kapselointi

Nyt kapseloidaan tämä EFace, jotta mustaa magiaa voidaan käyttää 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 hakee tyyppitiedot, jotka palautetaan TypeInfo-rakenteena. Nyt tätä TypeInfo-rakennetta käyttämällä tyypin hajautusarvoon, lajiin ja vertailufunktioon pääsee helposti käsiksi.

Hyödyntäminen

Hyödyntäminen on todella helppoa. Riittää, kun välittää any-tyyppisen muuttujan GetTypeInfo-funktiolle.

 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 liukulukuarvot välitetään GetTypeInfo-funktiolle, ja niiden tyyppitiedot tulostetaan. Arvoja verrataan sitten Equal-funktion avulla. Tämä mahdollistaa tyyppitietojen nopeamman saamisen ja tyyppien vertailun.

Lisäksi tätä menetelmää voidaan käyttää samalla tavalla rakenteiden, osoittimien ja rajapintojen kanssa, ja se voi ratkaista tyypillisen ongelman, joka liittyy tyypilliseen nil-arvoon (typed nil). Tämä johtuu siitä, että vaikka Hash on allokoitu, jos tyyppi on olemassa, This on 0, jos se on nil.

Johtopäätös

EFace-rajapinnalla on nykyään tärkeä rooli any-tyypin käsittelyssä Go-kielessä. Sen avulla voidaan käsitellä joustavasti ja nopeasti erilaisia arvoja sekä hankkia ja hyödyntää yksityiskohtaisia tyyppitietoja. On kuitenkin huomioitava, että tämä on suoritusajan sisäinen piilotettu ominaisuus, johon kielimäärittelyn muutokset voivat vaikuttaa.