GoSuda

Any is not Any, but Any is Any

By snowmerak
views ...

Any

In the Go language, the any type is an alias for interface{}, a magical interface capable of holding any type. This means that any type can be assigned to an any type variable.

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

As illustrated, the any type can accommodate values of various types, thus enabling the creation of flexible code. However, several considerations must be observed when utilizing the any type.

Type Assertion

To utilize the actual value from an any type variable, type assertion must be employed. Type assertion serves to inform the compiler that an any type variable is of a specific type.

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}

In the preceding example, a.(string) asserts that a is of type string. If a is not of type string, ok will be false, thereby preventing the program from panicking.

Type Switch

A type switch enables the execution of different operations based on the actual type of an any type variable. This proves beneficial when processing multiple types.

 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}

In the example above, a.(type) examines the actual type of a and executes the case corresponding to that type.

Reflection

By utilizing Go's reflect package, the type and value of an any type variable can be dynamically inspected and manipulated at runtime. This is advantageous when managing complex data structures.

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

In the preceding example, the reflect package is used to ascertain the type and value of a at runtime.

EFace

In the Go language, interfaces are categorized into two primary forms: EFace (Empty Interface) and IFace (Interface with Methods). Among these, EFace corresponds to the any type concept discussed thus far.

Structure

EFace is a specialized structure that exists exclusively within the Go runtime; therefore, a mimic will be created for its discussion here.

 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 comprises two fields:

  • Type: A pointer containing metadata regarding the actual type of the value.
  • Data: A pointer containing the actual value.

Internally, EFace manages type information through the runtimeType structure. This structure encompasses various metadata, including the size, alignment, and hash value of the type.

The fields of particular interest within this context are Kind_, Hash, and Equal. Kind_ indicates the type's category, while Hash and Equal are employed for comparing types and computing hash values within hash tables.

Wrapping

Let us now wrap this EFace to facilitate more convenient "dark magic."

 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}

The aforementioned function accepts an any type variable, extracts the internal EFace structure, and retrieves type information to return it as a TypeInfo structure. Now, TypeInfo can be readily utilized to access the type's hash value, kind, comparison function, and other attributes.

Application

Its application is remarkably straightforward. One merely passes an any type variable to the GetTypeInfo function.

 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}

In the example above, integer and floating-point values are passed to the GetTypeInfo function to display their respective type information, and the Equal function is used to compare the values. This method allows for faster retrieval of type information and type comparison.

Furthermore, this approach is equally applicable to structs, pointers, and interfaces, and it can resolve the frequently encountered issue of typed nils. While Hash will be assigned if a type exists, This will be zero if it is nil.

Conclusion

EFace currently plays a critical role in handling the any type within the Go language. It enables flexible and rapid processing of values of diverse types, and facilitates detailed acquisition and utilization of type information. However, it is imperative to note that this is a hidden runtime feature and may be subject to changes in the language specification.