GoSuda

Any no es Any, pero Any es Any

By snowmerak
views ...

Any

En el lenguaje Go, el tipo any es un alias de la interfaz mágica, interface{}, capaz de 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

De esta manera, el tipo any facilita la redacción de código flexible al permitir almacenar valores de tipologías diversas. No obstante, existen ciertas consideraciones que deben tenerse en cuenta al emplear el tipo any.

Type Assertion

Para utilizar el valor subyacente contenido en una variable de tipo any, es imperativo emplear la aserción de tipo (type assertion). La aserción de tipo cumple la función de notificar al compilador la naturaleza tipológica específica de la variable de tipo 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}

En el ejemplo precedente, a.(string) efectúa la aserción de que a posee el tipo string. Si a no es del tipo string, la variable ok adquiere el valor false, lo cual previene que el programa entre en estado de pánico (panic).

Type Switch

El type switch posibilita la ejecución de comportamientos divergentes en función del tipo real que contenga la variable de tipo any. Esto resulta provechoso cuando se requiere gestionar una pluralidad de tipologías.

 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 mostrado, a.(type) inspecciona el tipo real de a y ejecuta la rama (case) correspondiente a dicha tipología.

Reflection

La utilización del paquete reflect del lenguaje Go permite la inspección y manipulación dinámica del tipo y del valor de una variable de tipo any durante el tiempo de ejecución (runtime). Esto resulta útil 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 precedente, es posible verificar el tipo y el valor de a en tiempo de ejecución mediante el empleo del paquete reflect.

EFace

En el lenguaje Go, las interfaces se segmentan en dos formas principales: EFace (Empty Interface) y IFace (Interface with Methods). De estas, EFace constituye el concepto idéntico al tipo any que hemos abordado hasta el momento.

Estructura

Dado que EFace es una estructura especial que reside exclusivamente dentro del entorno de ejecución (runtime) del lenguaje Go, aquí procederemos a su tratamiento mediante la creación de una simulación (mimic).

 1type runtimeTypeMimic struct {
 2	Size_       uintptr
 3	PtrBytes    uintptr // número de bytes (prefijo) en el tipo que pueden contener punteros
 4	Hash        uint32  // hash del tipo; evita el cálculo en tablas hash
 5	TFlag       uint8   // banderas de información tipológica adicional
 6	Align_      uint8   // alineación de la variable con este tipo
 7	FieldAlign_ uint8   // alineación del campo de estructura con este tipo
 8	Kind_       uint8   // enumeración para C
 9	Equal       func(unsafe.Pointer, unsafe.Pointer) bool
10	GCData      *byte
11	Str         int32 // forma de cadena
12	PtrToThis   int32 // tipo para puntero a este tipo, puede ser cero
13}
14
15type eFaceMimic struct {
16	Type *runtimeTypeMimic
17	Data unsafe.Pointer
18}

EFace consta de dos campos:

  • Type: Un puntero que contiene metadatos relativos al tipo real del valor.
  • Data: Un puntero que almacena el valor efectivo.

Asimismo, internamente EFace administra la información tipológica a través de la estructura runtimeType. Esta estructura engloba diversos metadatos, tales como el tamaño del tipo, su alineación y su valor hash.

Dentro de esta, las secciones sobre las que debemos enfocar nuestra atención son los campos Kind_, Hash y Equal. Kind_ designa la índole del tipo, mientras que Hash y Equal se emplean para la comparación de tipos y el cálculo del valor hash en las tablas hash.

Empaquetamiento

Procedamos ahora a envolver (wrap) este EFace para posibilitar la ejecución de manipulaciones de bajo nivel con mayor facilidad.

 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 precedente recibe una variable de tipo any, extrae la estructura EFace interna, recupera la información tipológica contenida en ella y la retorna encapsulada en una estructura TypeInfo. Mediante este TypeInfo, es posible acceder fácilmente al valor hash, la índole y la función de comparación del tipo, entre otros atributos.

Aplicación

La aplicación práctica resulta sumamente sencilla. Basta con proveer la 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	// Comparar los dos valores utilizando la función Equal desde la información tipológica
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 precedente, se suministran valores enteros y de punto flotante a la función GetTypeInfo para imprimir su respectiva información tipológica, y se emplea la función Equal para realizar la comparación de dichos valores. Esto permite obtener información tipológica y efectuar comparaciones de tipos con mayor celeridad.

Además, este método puede aplicarse de manera idéntica a estructuras (structs), punteros e interfaces, y permite subsanar el problema del nil tipado (typed nil), el cual surge con relativa frecuencia. Esto se debe a que Hash estará asignado si el tipo existe, mientras que This será cero en caso de ser nil.

Conclusión

EFace desempeña un papel crucial en el manejo del tipo any dentro del lenguaje Go actual. Mediante esto, es posible procesar valores de tipologías variadas de forma flexible y expedita, logrando obtener y explotar información tipológica detallada. Sin embargo, dado que se trata de una funcionalidad oculta dentro del entorno de ejecución (runtime), puede verse influenciada por modificaciones en la especificación del lenguaje, por lo que se requiere cautela.