GoSuda

Any — это не Any, но Any есть Any

By snowmerak
views ...

Any

В языке Go тип any является псевдонимом для interface{}, магического интерфейса, который может содержать любой тип. То есть, переменной типа any может быть присвоен любой тип.

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

Таким образом, тип any может содержать значения различных типов, что позволяет писать гибкий код. Однако, при использовании типа any следует учитывать несколько моментов.

Type Assertion

Для использования фактического значения из переменной типа any необходимо использовать утверждение типа (type assertion). Утверждение типа сообщает компилятору, что переменная типа any имеет определённый тип.

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}

В приведённом выше примере a.(string) утверждает, что a имеет тип string. Если a не является типом string, ok станет false, что предотвратит панику программы.

Type Switch

Переключатель типов (type switch) позволяет выполнять различные действия в зависимости от фактического типа переменной any. Это полезно при обработке нескольких типов.

 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}

В приведённом выше примере a.(type) проверяет фактический тип a и выполняет соответствующий case.

Reflection

Использование пакета reflect языка Go позволяет динамически проверять и манипулировать типом и значением переменной any во время выполнения. Это полезно при работе со сложными структурами данных.

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

В приведённом выше примере пакет reflect используется для проверки типа и значения a во время выполнения.

EFace

В языке Go интерфейсы делятся на две основные формы: EFace (Empty Interface) и IFace (Interface with Methods). EFace — это та же концепция, что и тип any, которую мы рассматривали до сих пор.

Структура

EFace — это особая структура, которая существует только в среде выполнения языка Go, поэтому здесь мы создадим её имитацию.

 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 имеет два поля:

  • Type: указатель, содержащий метаданные о фактическом типе значения.
  • Data: указатель, содержащий фактическое значение.

EFace внутренне управляет информацией о типе через структуру runtimeType. Эта структура включает различные метаданные, такие как размер типа, выравнивание и хеш-значение.

В этом контексте следует обратить внимание на поля Kind_, Hash и Equal. Kind_ представляет собой вид типа, а Hash и Equal используются для сравнения типов и вычисления хеш-значений в хеш-таблицах.

Упаковка

Теперь давайте обернём этот EFace, чтобы можно было более удобно использовать "чёрную магию".

 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}

Приведённая выше функция принимает переменную типа any, извлекает внутреннюю структуру EFace и возвращает информацию о типе в виде структуры TypeInfo. Теперь, используя TypeInfo, можно легко получить доступ к хеш-значению, виду типа, функции сравнения и т.д.

Применение

Применение очень простое. Достаточно передать переменную типа any в функцию 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}

В приведённом выше примере целочисленные и числа с плавающей запятой передаются в функцию GetTypeInfo для вывода соответствующей информации о типе, а затем значения сравниваются с помощью функции Equal. Это позволяет быстрее получать информацию о типе и сравнивать типы.

Кроме того, этот подход может быть аналогично использован для структур, указателей, интерфейсов и других элементов, а также может решить часто возникающую проблему с типизированным nil (typed nil). Хотя Hash будет присвоен, если тип существует, This будет 0, если он nil.

Заключение

EFace играет важную роль в обработке типа any в современном языке Go. Он позволяет гибко и быстро обрабатывать значения различных типов, а также получать и использовать подробную информацию о типах. Однако следует отметить, что это скрытая функция внутри среды выполнения, и она может быть подвержена влиянию изменений в спецификации языка.