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

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, поэтому здесь мы будем работать с её мимикрией (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 содержит два поля:
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 значения сравниваются между собой. Таким образом, информацию о типе можно получить и сравнить быстрее.
Кроме того, этот подход можно так же применять к структурам, указателям, интерфейсам и т. д., а также решить распространённую проблему typed nil. Поле Hash будет заполнено, если тип задан, тогда как This равно нулю при nil, поэтому можно различить эти случаи.
Заключение
EFace играет важную роль при работе с типом any в современном Go. С его помощью различные значения можно обрабатывать гибко и быстро, а также получать и использовать подробную информацию о типе. Однако это скрытая функциональность среды выполнения, которая может измениться вместе с изменениями спецификации языка, поэтому к её использованию следует относиться внимательно.