GoSuda

Any is not Any, but Any is Any

By snowmerak
views ...

Any

En el lenguaje Go, el tipo any es un alias de la interfaz mágica interface{}, la cual puede contener cualquier tipo. Es decir, cualquier tipo puede ser asignado a una variable de tipo any.

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

Como se observa, el tipo any puede almacenar valores de diversos tipos, lo que permite escribir código flexible. No obstante, existen algunas consideraciones al utilizar el tipo any.

Type Assertion

Para utilizar el valor real de una variable de tipo any, es necesario emplear una aserción de tipo (type assertion). Una aserción de tipo notifica al compilador que la variable de tipo any es de un tipo específico.

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}

En el ejemplo anterior, a.(string) afirma que a es de tipo string. Si a no fuera de tipo string, ok sería false, lo que previene que el programa entre en pánico.

Type Switch

Un type switch permite ejecutar diferentes acciones dependiendo del tipo real de una variable any. Esto resulta útil cuando se requiere procesar múltiples tipos.

 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}

En el ejemplo anterior, a.(type) examina el tipo real de a y ejecuta el caso correspondiente a dicho tipo.

Reflection

El paquete reflect del lenguaje Go permite inspeccionar y manipular dinámicamente el tipo y el valor de una variable any en tiempo de ejecución. Esto es beneficioso al tratar con estructuras de datos complejas.

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

En el ejemplo anterior, el paquete reflect se utiliza para verificar el tipo y el valor de a en tiempo de ejecución.

EFace

En el lenguaje Go, las interfaces se dividen en dos formas principales: EFace (Empty Interface) e IFace (Interface with Methods). De estas, EFace es el mismo concepto que el tipo any que hemos abordado hasta ahora.

Estructura

EFace es una estructura especial que existe únicamente dentro del entorno de ejecución de Go, por lo que aquí crearemos un mimic para abordarla.

 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 consta de dos campos:

  • Type: Un puntero que contiene metadatos sobre el tipo real del valor.
  • Data: Un puntero que contiene el valor real.

Internamente, EFace gestiona la información de tipo a través de la estructura runtimeType. Esta estructura incluye diversos metadatos como el tamaño, la alineación y el valor hash del tipo.

Dentro de esta, los campos en los que debemos enfocarnos son Kind_, Hash y Equal. Kind_ indica la categoría del tipo, mientras que Hash y Equal se emplean para comparar tipos y calcular valores hash en tablas hash.

Envoltura

Ahora, vamos a envolver esta EFace para poder realizar operaciones avanzadas de manera más sencilla.

 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}

La función anterior toma una variable de tipo any, extrae la estructura EFace interna y recupera la información de tipo de esta para devolverla como una estructura TypeInfo. Ahora, con esta TypeInfo, se puede acceder fácilmente al valor hash, la categoría y la función de comparación del tipo.

Uso

El uso es muy sencillo. Simplemente se pasa una variable de tipo any a la función 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}

En el ejemplo anterior, se pasan valores enteros y de punto flotante a la función GetTypeInfo para imprimir la información de tipo de cada uno y se comparan los valores utilizando la función Equal. Esto permite obtener información de tipo y comparar tipos de manera más rápida.

Además, este método puede aplicarse de manera idéntica a estructuras, punteros e interfaces, y también puede resolver el problema del nil tipado (typed nil), que es un asunto que ocurre con cierta frecuencia. Aunque Hash se asignaría si existe un tipo, This sería 0 si es nil.

Conclusión

EFace desempeña un papel crucial en el manejo del tipo any en el lenguaje Go. Permite procesar valores de diversos tipos de manera flexible y rápida, y obtener y utilizar información detallada sobre los tipos. No obstante, dado que es una característica oculta dentro del entorno de ejecución, es importante tener precaución ya que puede verse afectada por cambios en la especificación del lenguaje.