GoSuda

Any är inte Any, men Any är Any

By snowmerak
views ...

Any

I Go-språket är typen any en alias för det magiska gränssnittet interface{}, som kan innehålla vilken typ som helst. Detta innebär att vilken typ som helst kan tilldelas en any-typvariabel.

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

Som framgår kan any-typen innehålla värden av olika typer, vilket möjliggör skapandet av flexibel kod. Det finns dock några punkter att beakta när man använder any-typen.

Type Assertion

För att använda det faktiska värdet från en any-typvariabel måste man använda "type assertion". Type assertion informerar kompilatorn att any-typvariabeln är av en specifik typ.

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}

I exemplet ovan asserterar a.(string) att a är av typen string. Om a inte är av typen string, kommer ok att vara false, vilket förhindrar att programmet panikerar.

Type Switch

"Type switch" möjliggör olika beteenden beroende på den faktiska typen av en any-typvariabel. Detta är användbart när flera typer behöver hanteras.

 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}

I exemplet ovan undersöker a.(type) den faktiska typen av a och exekverar det matchande fallet.

Reflection

Go-språkets reflect-paket kan användas för att dynamiskt inspektera och manipulera typen och värdet av en any-typvariabel vid körning. Detta är användbart när man hanterar komplexa datastrukturer.

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

I exemplet ovan kan reflect-paketet användas för att kontrollera typen och värdet av a vid körning.

EFace

I Go-språket delas gränssnitt in i två huvudformer: EFace (Empty Interface) och IFace (Interface with Methods). Av dessa är EFace identiskt med any-typen som vi har behandlat hittills.

Struktur

EFace är en speciell struktur som endast existerar inom Go-språkets runtime. Därför kommer vi här att skapa en imitation för att behandla den.

 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 har två fält:

  • Type: En pekare som innehåller metadata om värdets faktiska typ.
  • Data: En pekare som innehåller det faktiska värdet.

Internt hanterar EFace typinformation via runtimeType-strukturen. Denna struktur innehåller olika metadata, såsom typens storlek, justering och hashvärde.

De delar vi bör fokusera på är fälten Kind_, Hash och Equal. Kind_ indikerar typens kategori, medan Hash och Equal används för att jämföra typer och beräkna hashvärden i hashtabeller.

Inkapsling

Låt oss nu inkapsla denna EFace för att göra det enklare att använda magin.

 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}

Funktionen ovan tar en any-typvariabel, extraherar den interna EFace-strukturen och hämtar typinformationen därifrån för att returnera den som en TypeInfo-struktur. Nu kan vi enkelt komma åt typens hashvärde, kategori, jämförelsefunktioner med mera med hjälp av denna TypeInfo.

Användning

Användningen är verkligen enkel. Man skickar helt enkelt en any-typvariabel till funktionen 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}

I exemplet ovan skickas ett heltal och ett flyttalsvärde till funktionen GetTypeInfo för att skriva ut respektive typinformation, och sedan jämförs värdena med funktionen Equal. Detta möjliggör snabbare åtkomst till typinformation och jämförelse av typer.

Dessutom kan denna metod användas på samma sätt för strukturer, pekare och gränssnitt, och den kan även lösa problemet med "typed nil", som är en inte ovanlig fråga. Hash skulle tilldelas om det finns en typ, men This är 0 om det är nil.

Slutsats

EFace spelar för närvarande en viktig roll i hanteringen av any-typen i Go-språket. Genom EFace kan man flexibelt och snabbt hantera värden av olika typer, samt erhålla och använda detaljerad typinformation. Det bör dock noteras att detta är en dold funktion inom runtime och kan påverkas av ändringar i språkspecifikationen.