GoSuda

Any není Any, ale Any je Any

By snowmerak
views ...

Any

V jazyce Go je typ any aliasem pro magický interface interface{}, který dokáže pojmout jakýkoli typ. To znamená, že jakýkoli typ lze přiřadit proměnné typu any.

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

Typ any tak umožňuje psát flexibilní kód, který dokáže pracovat s různými typy hodnot. Při použití typu any je však třeba mít na paměti několik věcí.

Type Assertion

Chcete-li použít skutečnou hodnotu z proměnné typu any, musíte použít tzv. type assertion. Type assertion informuje kompilátor, že proměnná typu any je určitého 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 tomto příkladu a.(string) tvrdí, že a je typu string. Pokud a není typu string, ok bude false a program se tak vyhne panice.

Type Switch

Type switch umožňuje provádět různé akce v závislosti na skutečném typu proměnné any. To je užitečné, když potřebujete 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}

V tomto příkladu a.(type) kontroluje skutečný typ a a spustí odpovídající case.

Reflection

Pomocí balíčku reflect v jazyce Go můžete dynamicky kontrolovat a manipulovat s typem a hodnotou proměnné typu any za běhu programu. To 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())

V tomto příkladu lze pomocí balíčku reflect za běhu programu zkontrolovat typ a hodnotu a.

EFace

V jazyce Go se rozlišují dva hlavní typy rozhraní: EFace (Empty Interface) a IFace (Interface with Methods). EFace je stejný koncept jako typ any, kterým jsme se dosud zabývali.

Struktura

EFace je speciální struktura, která existuje pouze v rámci runtime jazyka Go, proto zde vytvoříme její mimikry.

 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.

Interně EFace spravuje informace o typech prostřednictvím struktury runtimeType. Tato struktura zahrnuje různá metadata, jako je velikost typu, zarovnání a hodnota hash.

V této struktuře si všimneme polí Kind_, Hash a Equal. Kind_ označuje druh typu, zatímco Hash a Equal se používají k porovnávání typů a výpočtu hash hodnot v hash tabulkách.

Obalení

Nyní zabalíme tento EFace, abychom mohli pohodlněji používat "č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řijímá proměnnou typu any, extrahuje z ní vnitřní strukturu EFace a z ní získává informace o typu, které vrací jako strukturu TypeInfo. Nyní lze pomocí této TypeInfo snadno přistupovat k hash hodnotě, druhu a porovnávací funkci typu.

Použití

Použití je velmi 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}

V tomto příkladu jsou celočíselné a desetinné hodnoty předány funkci GetTypeInfo, aby se vypsaly jejich typové informace, a poté jsou hodnoty porovnány pomocí funkce Equal. To umožňuje rychleji získat typové informace a porovnávat typy.

Tato metoda může být také použita pro struktury, ukazatele a rozhraní a dokáže řešit často se vyskytující problém s "typed nil" (nil s typem). Hash bude přiřazen, pokud existuje typ, ale This bude 0, pokud je nil.

Závěr

EFace hraje v současném jazyce Go důležitou roli při práci s typem any. Umožňuje flexibilní a rychlé zpracování hodnot různých typů a podrobné získání a využití informací o typech. Je však třeba poznamenat, že se jedná o skrytou funkci v rámci runtime a může být ovlivněna změnami ve specifikaci jazyka, proto je nutná opatrnost.