GoSuda

Any n'est pas Any, mais Any est Any

By snowmerak views ...

Any

En Go, le type any est un alias de interface{}, une interface magique capable de contenir n'importe quel type. Autrement dit, une variable de type any peut recevoir une valeur de n'importe quel type.

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

Ainsi, le type any peut contenir des valeurs de types variés, ce qui permet d'écrire du code souple. Toutefois, l'utilisation du type any exige certaines précautions.

Type Assertion

Pour utiliser la valeur réelle contenue dans une variable de type any, il faut recourir à l'assertion de type (type assertion). L'assertion de type indique au compilateur que la variable de type any correspond à un type spécifique.

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}

Dans l'exemple ci-dessus, a.(string) affirme que a est de type string. Si a n'est pas de type string, ok vaut false, ce qui permet d'éviter que le programme ne entre en panique.

Type Switch

Le sélecteur de type (type switch) permet d'exécuter différents comportements selon le type réel d'une variable de type any. Il est utile lorsqu'il faut traiter plusieurs 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}

Dans l'exemple ci-dessus, a.(type) examine le type réel de a et exécute le cas correspondant à ce type.

Reflection

Le package reflect de Go permet d'examiner et de manipuler dynamiquement, à l'exécution, le type et la valeur d'une variable de type any. Cela s'avère utile lorsqu'on manipule des structures de données complexes.

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

Dans l'exemple ci-dessus, le package reflect permet de vérifier le type et la valeur de a à l'exécution.

EFace

En Go, les interfaces se répartissent en deux formes principales : EFace (Empty Interface) et IFace (Interface with Methods). Parmi celles-ci, EFace correspond au concept identique à celui du type any que nous avons abordé jusqu'ici.

Structure

EFace est une structure particulière qui n'existe qu'à l'intérieur du runtime de Go ; nous allons donc la manipuler à travers un mimétisme (mimic).

 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 possède deux champs :

  • Type : pointeur vers les métadonnées décrivant le type réel de la valeur.
  • Data : pointeur vers la valeur réelle.

De plus, EFace gère les informations de type de manière interne au moyen de la structure runtimeType. Cette structure contient diverses métadonnées, telles que la taille, l'alignement et la valeur de hachage du type.

Les champs sur lesquels nous devons porter notre attention sont Kind_, Hash et Equal. Kind_ indique la catégorie du type, tandis que Hash et Equal servent à comparer les types et à calculer leur valeur de hachage dans les tables de hachage.

Encapsulation

Enveloppons maintenant cette EFace afin de pouvoir utiliser plus facilement cette magie noire.

 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 fonction ci-dessus reçoit une variable de type any, extrait la structure EFace interne, puis en tire les informations de type qu'elle renvoie sous forme de structure TypeInfo. Il devient alors aisé d'accéder au hachage du type, à sa catégorie, à sa fonction de comparaison, etc. au moyen de ce TypeInfo.

Utilisation

L'utilisation est très simple : il suffit de transmettre une variable de type any à la fonction 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}

Dans l'exemple ci-dessus, les valeurs entière et à virgule flottante sont transmises à la fonction GetTypeInfo afin d'afficher leurs informations de type respectives, puis la fonction Equal sert à comparer les valeurs. Grâce à cette approche, on peut obtenir plus rapidement les informations de type et comparer les types.

De plus, cette méthode s'applique de la même manière aux structures, aux pointeurs et aux interfaces, et permet aussi de résoudre le problème fréquent du typed nil. Hash reçoit une valeur dès lors que le type existe, alors que This vaut 0 lorsqu'il est nil.

Conclusion

EFace joue aujourd'hui un rôle essentiel dans la manipulation du type any en Go. Grâce à elle, on peut traiter des valeurs de types variés de manière souple et rapide, et obtenir et exploiter en détail les informations de type. Toutefois, il s'agit d'une fonctionnalité cachée au sein du runtime, susceptible d'être affectée par les évolutions de la spécification du langage ; il convient donc de rester prudent.