GoSuda

Any no es Any, pero Any es Any

By snowmerak views ...

Any

En el lenguaje Go, el tipo any es un alias de interface{}, una interfaz mágica capaz de contener cualquier tipo. En otras palabras, cualquier tipo puede asignarse a una variable de tipo any.

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

Así, el tipo any puede contener valores de diversos tipos, lo que permite escribir código flexible. Sin embargo, al utilizar el tipo any hay algunos puntos que requieren atención.

Type Assertion

Para utilizar el valor real de una variable de tipo any, es necesario emplear una aserción de tipo (type assertion). La aserción de tipo cumple la función de indicar al compilador que la variable 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 es de tipo string, ok será false, lo que permite evitar que el programa entre en pánico.

Type Switch

El type switch (switch de tipos) permite ejecutar distintos comportamientos según el tipo real de una variable de tipo any. Resulta útil cuando es necesario procesar varios 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 ese tipo.

Reflection

Mediante el paquete reflect de Go, es posible examinar y manipular dinámicamente en tiempo de ejecución el tipo y el valor de una variable de tipo any. Esto resulta útil al trabajar 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, mediante el paquete reflect se pueden comprobar en tiempo de ejecución el tipo y el valor de a.

EFace

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

Estructura

EFace es una estructura especial que existe únicamente dentro del runtime de Go, por lo que aquí trabajaremos con un mimic (imitación) de la misma.

 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 tiene dos campos:

  • Type: es un puntero que contiene los metadatos sobre el tipo real del valor.
  • Data: es un puntero que contiene el valor real.

Además, EFace gestiona la información de tipos internamente mediante la estructura runtimeType. Esta estructura contiene diversos metadatos, como el tamaño del tipo, su alineación y su valor hash.

Dentro de ella, los aspectos que debemos observar son los campos Kind_, Hash y Equal. Kind_ indica la clase del tipo, mientras que Hash y Equal se utilizan para calcular valores hash y comparar tipos en las tablas hash.

Envoltorio

Ahora envolvamos este EFace para poder utilizar la magia negra de forma más cómoda.

 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 recibe una variable de tipo any, extrae la estructura EFace interna, obtiene de ella la información de tipo y la devuelve como una estructura TypeInfo. Ahora, con este TypeInfo, se puede acceder fácilmente al valor hash del tipo, su clase, la función de comparación, etc.

Uso

El uso es realmente sencillo. Basta con pasar 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 a la función GetTypeInfo un valor entero y un valor de punto flotante para mostrar la información de tipo de cada uno, y se utiliza la función Equal para comparar los valores. De este modo, es posible obtener la información de tipo y compararla de forma más rápida.

Además, este método también puede aplicarse a estructuras, punteros, interfaces, etc., y permite resolver el problema, nada infrecuente, del nil con tipo (typed nil). Hash tiene asignado un valor siempre que existe un tipo, mientras que This vale 0 si es nil, por lo que es posible distinguirlos.

Conclusión

EFace desempeña un papel importante en el manejo del tipo any en el lenguaje Go actual. Gracias a él, es posible procesar valores de diversos tipos de forma flexible y rápida, así como obtener y utilizar información detallada sobre los tipos. No obstante, se trata de una funcionalidad oculta dentro del runtime, que puede verse afectada por cambios en la especificación del lenguaje, por lo que debe utilizarse con precaución.