GoSuda

Any nie jest Any, ale Any jest Any

By snowmerak views ...

Any

W języku Go typ any jest aliasem interface{}, magicznego interfejsu, który może przechowywać wartości dowolnego typu. Innymi słowy, do zmiennej typu any można przypisać wartość dowolnego typu.

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

Jak widać, typ any pozwala przechowywać wartości różnych typów, co umożliwia pisanie elastycznego kodu. Jednak podczas korzystania z typu any należy zwrócić uwagę na kilka kwestii.

Type Assertion

Aby uzyskać rzeczywistą wartość ze zmiennej typu any, należy skorzystać z asercji typu (type assertion). Asercja typu 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) potwierdza, że a jest typu string. Jeśli a nie jest typu string, wartość ok wynosi false, co pozwala uniknąć wpadnięcia programu w panikę.

Type Switch

Instrukcja przełącznika typu (type switch) pozwala wykonywać różne działania w zależności od rzeczywistego typu zmiennej typu any. Jest to przydatne, gdy należy 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 przypadek odpowiadający temu typowi.

Reflection

Korzystając z pakietu reflect języka Go, można dynamicznie sprawdzać i modyfikować typ oraz wartość zmiennej typu any w czasie działania programu. Jest to przydatne przy 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 służy do sprawdzenia typu i wartości a w czasie działania programu.

EFace

W języku Go interfejsy dzielą się na dwie główne formy: EFace (Empty Interface) oraz IFace (Interface with Methods). Spośród nich EFace jest pojęciem tożsamym z typem any, o którym mówiliśmy dotychczas.

Struktura

EFace jest specjalną strukturą istniejącą wyłącznie wewnątrz środowiska uruchomieniowego (runtime) Go, dlatego tutaj posłużymy się imitacją (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 posiada dwa pola:

  • Type: wskaźnik zawierający metadane o rzeczywistym typie wartości.
  • Data: wskaźnik zawierający rzeczywistą wartość.

Ponadto EFace wewnętrznie zarządza informacjami o typie za pomocą struktury runtimeType. Struktura ta zawiera różne metadane, takie jak rozmiar typu, wyrównanie oraz wartość skrótu (hash).

Na co warto zwrócić szczególną uwagę w tej strukturze, to pola Kind_, Hash oraz Equal. Kind_ określa rodzaj typu, natomiast Hash i Equal są wykorzystywane do obliczania wartości skrótu oraz porównywania typów w tablicach haszujących.

Opakowanie

Teraz owińmy EFace w wrapper, aby ułatwić sobie korzystanie z 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, 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, jego rodzaju, funkcji porównującej itd.

Zastosowanie

Użycie 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, eface.This))
15}

W powyższym przykładzie wartości całkowitą i zmiennoprzecinkową przekazujemy do funkcji GetTypeInfo, aby wypisać informacje o ich typach, a następnie za pomocą funkcji Equal porównujemy wartości. Dzięki temu można szybciej uzyskać informacje o typie oraz porównywać typy.

Ta metoda może być również stosowana w przypadku struktur, wskaźników, interfejsów itp., a także pozwala rozwiązać częsty problem typed nil. Pole Hash jest przypisane, jeśli typ istnieje, natomiast This ma wartość 0 (nil), gdy wskaźnik jest pusty.

Podsumowanie

EFace odgrywa istotną rolę w obsłudze typu any w języku Go obecnie. Pozwala to elastycznie i szybko przetwarzać wartości różnych typów oraz szczegółowo uzyskiwać i wykorzystywać informacje o typach. Należy jednak pamiętać, że jest to funkcjonalność ukryta w środowisku uruchomieniowym, a więc może ulec zmianie wraz ze zmianami specyfikacji języka.