GoSuda

Any non è Any, ma Any è Any

By snowmerak
views ...

Any

Nel linguaggio Go, il tipo any è un alias per l'interfaccia magica interface{}, capace di contenere qualsiasi tipo. In altre parole, qualsiasi tipo può essere assegnato a una variabile di tipo any.

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

Come dimostrato, il tipo any può contenere valori di diversi tipi, consentendo la scrittura di codice flessibile. Tuttavia, ci sono alcune considerazioni da tenere presenti quando si utilizza il tipo any.

Type Assertion

Per utilizzare il valore effettivo da una variabile di tipo any, è necessario ricorrere all'asserzione di tipo (type assertion). L'asserzione di tipo informa il compilatore che una variabile di tipo any è di un tipo specifico.

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}

Nell'esempio precedente, a.(string) asserisce che a è di tipo string. Se a non fosse di tipo string, ok diventerebbe false, prevenendo un panic del programma.

Type Switch

Il type switch consente di eseguire azioni diverse a seconda del tipo effettivo di una variabile any. Questo è utile quando è necessario gestire più tipi.

 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}

Nell'esempio precedente, a.(type) esamina il tipo effettivo di a ed esegue il caso corrispondente a quel tipo.

Reflection

Utilizzando il pacchetto reflect del linguaggio Go, è possibile ispezionare e manipolare dinamicamente il tipo e il valore delle variabili di tipo any a runtime. Questo è particolarmente utile nella gestione di strutture dati complesse.

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

Nell'esempio precedente, il pacchetto reflect viene utilizzato per determinare il tipo e il valore di a a runtime.

EFace

Nel linguaggio Go, le interfacce si dividono in due forme principali: EFace (Empty Interface) e IFace (Interface with Methods). Tra queste, EFace è equivalente al concetto di tipo any che abbiamo trattato finora.

Struttura

EFace è una struttura speciale che esiste solo all'interno del runtime del linguaggio Go; pertanto, ne creeremo un "mimic" per la trattazione.

 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}

Un EFace è composto da due campi:

  • Type: un puntatore che contiene i metadati sul tipo effettivo del valore.
  • Data: un puntatore che contiene il valore effettivo.

EFace gestisce internamente le informazioni sul tipo tramite la struttura runtimeType. Questa struttura include vari metadati come la dimensione, l'allineamento e il valore hash del tipo.

All'interno di questa struttura, i campi Kind_, Hash ed Equal meritano particolare attenzione. Kind_ indica la categoria del tipo, mentre Hash ed Equal sono utilizzati per confrontare i tipi e calcolare i valori hash nelle tabelle hash.

Avvolgimento

Ora, avvolgiamo questo EFace per poter utilizzare la magia nera più comodamente.

 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 funzione sopra riportata accetta una variabile di tipo any, estrae la struttura EFace interna e recupera le informazioni sul tipo, restituendole come struttura TypeInfo. Ora, utilizzando questa TypeInfo, è possibile accedere facilmente al valore hash del tipo, alla sua categoria, alla funzione di confronto e così via.

Utilizzo

L'utilizzo è estremamente semplice. Basta passare una variabile di tipo any alla funzione 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, eface2.This))
15}

Nell'esempio precedente, valori interi e in virgola mobile vengono passati alla funzione GetTypeInfo per stampare le rispettive informazioni sul tipo e i valori vengono confrontati utilizzando la funzione Equal. Ciò consente di ottenere informazioni sul tipo e confrontare i tipi in modo più rapido.

Inoltre, questo approccio può essere applicato in modo identico a struct, puntatori e interfacce, e può risolvere il problema del "nil tipizzato" (typed nil), una questione non infrequente. Sebbene Hash sia allocato se il tipo esiste, This sarà zero se è nil.

Conclusione

EFace svolge un ruolo cruciale nella gestione del tipo any nel linguaggio Go attuale. Esso consente una gestione flessibile e rapida di valori di diversi tipi, oltre a ottenere e utilizzare informazioni dettagliate sui tipi. Tuttavia, poiché si tratta di una funzionalità nascosta all'interno del runtime, è importante prestare attenzione in quanto potrebbe essere influenzata da modifiche alle specifiche del linguaggio.