GoSuda

Any är inte Any, men Any är Any

By snowmerak views ...

Any

I Go-språket är any-typen ett alias för interface{}, ett magiskt gränssnitt som kan innehålla vilken typ som helst. Det innebär att vilken typ som helst kan tilldelas en variabel av typen any.

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

Således gör any-typen det möjligt att skriva flexibel kod, eftersom den kan innehålla värden av många olika typer. Vid användning av any-typen finns dock några saker att vara uppmärksam på.

Type Assertion

För att använda det faktiska värdet i en variabel av typen any måste man använda typpåstående (type assertion). Typpåstående informerar kompilatorn om att variabeln av typen any har en viss 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 påstår a.(string) att a är av typen string. Om a inte är av typen string blir ok false, vilket förhindrar att programmet går i panik.

Type Switch

Typväxling (type switch) gör det möjligt att utföra olika åtgärder beroende på den faktiska typen hos en variabel av typen any. Detta är användbart när flera typer måste 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 kontrollerar a.(type) den faktiska typen av a och kör därefter det case som motsvarar den typen.

Reflection

Med hjälp av paketet reflect i Go-språket kan man dynamiskt undersöka och manipulera typen och värdet hos en variabel av typen any under körning. Detta är användbart när komplexa datastrukturer hanteras.

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 använder vi paketet reflect för att under körning kontrollera typen och värdet hos a.

EFace

I Go-språket delas gränssnitt upp i två huvudsakliga former: EFace (Empty Interface) och IFace (Interface with Methods). Bland dessa är EFace detsamma som det any-typen vi har behandlat hittills.

Struktur

EFace är en särskild struktur som endast existerar inom Go-språkets runtime, därför kommer vi här att skapa en mimic för att arbeta med 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.

Dessutom hanterar EFace typinformation internt genom strukturen runtimeType. Denna struktur innehåller olika metadata, såsom typens storlek, justering och hashvärde.

Det som vi bör uppmärksamma i detta är fälten Kind_, Hash och Equal. Kind_ anger typens slag, medan Hash och Equal används för att jämföra typer och beräkna hashvärden i hashtabeller.

Inslagning

Låt oss nu slå in denna EFace i ett omslag så att vi kan använda den svarta magin bekvämare.

 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 emot en variabel av typen any, extraherar den interna EFace-strukturen, hämtar typinformationen och returnerar den som en struktur TypeInfo. Nu kan vi enkelt komma åt typens hashvärde, slag, jämförelsefunktion med mera genom denna TypeInfo.

Användning

Användningen är verkligen enkel. Man behöver bara skicka en variabel av typen any 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	// Jämför de två värdena med Equal-funktionen från typinformationen
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 skickar vi ett heltal och ett flyttal till funktionen GetTypeInfo, skriver ut respektive typinformation och jämför värdena med funktionen Equal. På så sätt kan vi snabbare få typinformation och jämföra typer.

Denna metod kan dessutom användas på samma sätt för strukturer, pekare, gränssnitt och liknande, och den kan även lösa det vanligt förekommande problemet med typed nil. Hash har ett tilldelat värde så länge typen finns, medan This är 0 om värdet är nil.

Slutsats

EFace spelar en viktig roll för hanteringen av any-typen i Go-språket i dagsläget. Genom den kan man hantera värden av många olika typer på ett flexibelt och effektivt sätt samt erhålla och använda detaljerad typinformation. Det bör dock beaktas att detta är en funktion som är dold inom runtime och därför kan påverkas av förändringar i språkspecifikationen.