GoSuda

Any nie je Any, ale Any je Any

By snowmerak
views ...

Any

V jazyku Go je typ any aliasom pre čarovné rozhranie interface{}, ktoré dokáže pojať akýkoľvek typ. To znamená, že akýkoľvek typ môže byť priradený premennej typu any.

1var a any
2a = 42          // int
3a = "hello"     // string
4a = true        // bool
5a = 3.14        // float64

Ako je vidieť, typ any umožňuje písať flexibilný kód tým, že dokáže pojať hodnoty rôznych typov. Pri používaní typu any je však potrebné zvážiť niekoľko vecí.

Type Assertion

Na použitie skutočnej hodnoty z premennej typu any je potrebné použiť type assertion. Type assertion informuje kompilátor, že premenná typu any je konkrétneho typu.

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) potvrdzuje, že a je typu string. Ak a nie je typu string, ok bude false, čím sa zabráni panike programu.

Type Switch

Type switch umožňuje vykonávať rôzne akcie v závislosti od skutočného typu premennej typu any. To je užitočné pri spracovaní viacerých 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íslušný case.

Reflection

Balík reflect v jazyku Go umožňuje dynamicky kontrolovať a manipulovať s typom a hodnotou premennej typu any počas behu. To 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 je možné pomocou balíka reflect počas behu overiť typ a hodnotu a.

EFace

V jazyku Go sa rozhrania delia na dve hlavné formy: EFace (Empty Interface) a IFace (Interface with Methods). EFace je rovnaký koncept ako typ any, ktorým sme sa zaoberali doteraz.

Štruktúra

EFace je špeciálna štruktúra, ktorá existuje iba v rámci runtime jazyka Go, takže tu vytvoríme jej napodobeninu (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ľ obsahujúci metadáta o skutočnom type hodnoty.
  • Data: Ukazovateľ obsahujúci skutočnú hodnotu.

EFace interne spravuje informácie o type prostredníctvom štruktúry runtimeType. Táto štruktúra obsahuje rôzne metadáta, ako je veľkosť, zarovnanie a hash hodnota typu.

V tejto časti by sme sa mali zamerať na polia Kind_, Hash a Equal. Kind_ predstavuje druh typu a Hash a Equal sa používajú na porovnávanie typov a výpočet hash hodnôt v hash tabuľkách.

Obalenie

Teraz obalíme tento EFace, aby sme mohli ľahšie vykonávať „čiernu mágiu“.

 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}

Vyššie uvedená funkcia prijíma premennú typu any, extrahuje internú štruktúru EFace a z nej vyberie informácie o type, ktoré vráti ako štruktúru TypeInfo. Teraz je možné pomocou TypeInfo ľahko pristupovať k hash hodnote, druhu a porovnávacej funkcii typu.

Použitie

Použitie je skutočne 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 sa celočíselné a desatinné hodnoty odovzdávajú funkcii GetTypeInfo na výstup ich príslušných informácií o type a hodnoty sa porovnávajú pomocou funkcie Equal. To umožňuje rýchlejší prístup k informáciám o type a porovnávanie typov.

Okrem toho je tento prístup použiteľný aj pre štruktúry, ukazovatele a rozhrania a dokáže vyriešiť problém „typed nil“, ktorý sa často vyskytuje. Hash bude priradený, ak existuje typ, ale This bude 0, ak je nil.

Záver

EFace v súčasnosti hrá dôležitú úlohu pri spracovaní typu any v jazyku Go. Umožňuje flexibilné a rýchle spracovanie hodnôt rôznych typov a poskytuje podrobné informácie o type, ktoré možno využiť. Je však potrebné poznamenať, že ide o skrytú funkciu v rámci runtime, ktorá môže byť ovplyvnená zmenami špecifikácie jazyka, preto je potrebná opatrnosť.