GoSuda

Any není Any, ale Any je Any

By snowmerak views ...

Any

V jazyce Go je typ any aliasem pro interface{}, kouzelné rozhraní, které dokáže uchovat hodnoty libovolného typu. Jinými slovy, do proměnné typu any lze přiřadit hodnotu jakéhokoli typu.

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

Takto typ any umožňuje uchovávat hodnoty různých typů, což umožňuje psát flexibilní kód. Při používání typu any je však třeba dbát na několik věcí.

Type Assertion

Chcete-li použít skutečnou hodnotu v proměnné typu any, je nutné použít typovou assertion (type assertion). Typová assertion slouží k tomu, aby sdělila kompilátoru, že proměnná typu any má konkrétní typ.

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}

Ve výše uvedeném příkladu a.(string) tvrdí, že a je typu string. Pokud a není typu string, ok má hodnotu false a lze tak zabránit tomu, aby program zpanikařil.

Type Switch

Přepínač typů (type switch) umožňuje provádět různé operace podle skutečného typu proměnné typu any. Je užitečný, když je potřeba zpracovat více typů.

 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}

Ve výše uvedeném příkladu a.(type) kontroluje skutečný typ a a provede větev odpovídající tomuto typu.

Reflection

Balíček reflect v jazyce Go umožňuje dynamicky zkoumat a upravovat typ a hodnotu proměnné typu any za běhu programu. Je užitečný při práci se složitými datovými strukturami.

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())

Ve výše uvedeném příkladu lze pomocí balíčku reflect ověřit typ a hodnotu a za běhu programu.

EFace

V jazyce Go se rozhraní dělí na dvě hlavní formy: EFace (Empty Interface) a IFace (Interface with Methods). Z nich EFace odpovídá pojmu typu any, kterým jsme se zabývali dosud.

Struktura

EFace je speciální struktura existující pouze uvnitř běhového prostředí (runtime) jazyka Go, proto ji zde budeme zpracovávat pomocí napodobeniny (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á dvě pole:

  • Type: ukazatel obsahující metadata o skutečném typu hodnoty.
  • Data: ukazatel obsahující skutečnou hodnotu.

EFace interně spravuje informace o typu prostřednictvím struktury runtimeType. Tato struktura obsahuje různá metadata, například velikost typu, zarovnání a hodnotu hash.

Na co bychom se v této struktuře měli zaměřit, jsou pole Kind_, Hash a Equal. Kind_ označuje druh typu, zatímco Hash a Equal se používají k výpočtu hodnoty hash a k porovnání typů v hash tabulkách.

Obalení

Nyní tento EFace obalíme, abychom s ním mohli pohodlněji pracovat a provádět černou magii.

 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}

Výše uvedená funkce přijme proměnnou typu any, extrahuje z ní interní strukturu EFace, získá z ní informace o typu a vrátí je jako strukturu TypeInfo. Pomocí TypeInfo nyní snadno přistoupíme k hodnotě hash typu, jeho druhu, porovnávací funkci a dalším údajům.

Použití

Použití je opravdu jednoduché. Stačí předat proměnnou typu any funkci 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}

Ve výše uvedeném příkladu předáváme celočíselnou hodnotu a hodnotu s plovoucí desetinnou čárkou funkci GetTypeInfo, vypisujeme jejich informace o typu a pomocí funkce Equal porovnáváme hodnoty. Díky tomu lze rychleji získat informace o typu a typy porovnávat.

Tento způsob lze také použít u struktur, ukazatelů, rozhraní a podobně a lze jím řešit i běžně se vyskytující problém typed nil. Hash bude přiřazen, pokud typ existuje, zatímco This bude nulové (0), pokud je hodnota nil.

Závěr

EFace hraje v současném Go důležitou roli při práci s typem any. Umožňuje zpracovávat hodnoty různých typů flexibilně a rychle a získávat podrobné informace o typu a využívat je. Je však třeba mít na paměti, že jde o funkcionalitu skrytou uvnitř běhového prostředí, a proto může být ovlivněna změnami jazykové specifikace.