GoSuda

Any nie jest Any, ale Any jest Any

By snowmerak
views ...

Any

W języku Go, typ any jest aliasem dla magicznego interfejsu interface{}, który może przechowywać dowolny typ. Oznacza to, że każdą zmienną dowolnego typu można przypisać do zmiennej typu any.

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

W ten sposób typ any pozwala na tworzenie elastycznego kodu, ponieważ może przechowywać wartości różnych typów. Należy jednak pamiętać o kilku kwestiach podczas używania typu any.

Type Assertion

Aby użyć rzeczywistej wartości ze zmiennej typu any, należy zastosować twierdzenie o typie (type assertion). Twierdzenie o typie informuje kompilator, że zmienna typu any jest określonego 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}

W powyższym przykładzie a.(string) twierdzi, że a jest typu string. Jeśli a nie jest typu string, ok przyjmie wartość false, co zapobiegnie panice programu.

Type Switch

Przełącznik typu (type switch) umożliwia wykonanie różnych działań w zależności od rzeczywistego typu zmiennej any. Jest to przydatne, gdy trzeba obsłużyć wiele typów.

 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}

W powyższym przykładzie a.(type) sprawdza rzeczywisty typ a i wykonuje odpowiednią gałąź case.

Reflection

Pakiet reflect w języku Go umożliwia dynamiczne sprawdzanie i manipulowanie typem oraz wartością zmiennej typu any w czasie wykonania. Jest to przydatne podczas pracy ze złożonymi strukturami danych.

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

W powyższym przykładzie pakiet reflect jest używany do sprawdzania typu i wartości a w czasie wykonania.

EFace

W języku Go interfejsy dzielą się na dwie główne formy: EFace (Empty Interface) i IFace (Interface with Methods). EFace jest tożsamy z pojęciem typu any, które do tej pory omawialiśmy.

Struktura

EFace jest specjalną strukturą, która istnieje tylko w środowisku wykonawczym języka Go, dlatego w tym miejscu stworzymy jej mimikę.

 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 składa się z dwóch pól:

  • Type: Wskaźnik zawierający metadane dotyczące rzeczywistego typu wartości.
  • Data: Wskaźnik zawierający rzeczywistą wartość.

EFace wewnętrznie zarządza informacjami o typie za pośrednictwem struktury runtimeType. Struktura ta zawiera różnorodne metadane, takie jak rozmiar typu, wyrównanie i wartość skrótu.

W tym kontekście należy zwrócić uwagę na pola Kind_, Hash i Equal. Kind_ reprezentuje rodzaj typu, a Hash i Equal są używane do porównywania typów i obliczania wartości skrótu w tablicach skrótów.

Opakowanie

Teraz opakujmy ten EFace, aby ułatwić wykonywanie "czarnej 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}

Powyższa funkcja przyjmuje zmienną typu any, wyodrębnia wewnętrzną strukturę EFace, a następnie pobiera z niej informacje o typie i zwraca je jako strukturę TypeInfo. Dzięki temu można łatwo uzyskać dostęp do wartości skrótu typu, rodzaju i funkcji porównania.

Zastosowanie

Zastosowanie jest bardzo proste. Wystarczy przekazać zmienną typu any do funkcji 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, eface2.This))
15}

W powyższym przykładzie wartości całkowite i zmiennoprzecinkowe są przekazywane do funkcji GetTypeInfo w celu wyświetlenia informacji o ich typach, a następnie używana jest funkcja Equal do porównania wartości. Pozwala to na szybsze uzyskiwanie informacji o typie i porównywanie typów.

Ponadto, ta metoda może być stosowana do struktur, wskaźników i interfejsów, a także może rozwiązać często występujący problem z nilami typowanymi (typed nil). Hash będzie przypisany, jeśli typ istnieje, ale This będzie równe 0, jeśli jest nil.

Wnioski

EFace odgrywa obecnie istotną rolę w obsłudze typu any w języku Go. Umożliwia elastyczne i szybkie przetwarzanie wartości różnych typów, a także uzyskiwanie i wykorzystywanie szczegółowych informacji o typie. Należy jednak pamiętać, że jest to ukryta funkcja w środowisku wykonawczym i może być podatna na zmiany w specyfikacji języka.