« Any » n'est pas « Any », mais « Any » est « Any »
Any
En Go, le type any est un alias pour l'interface magique interface{}, capable de contenir n'importe quel type. Cela signifie que n'importe quel type peut être assigné à une variable de type any.
1var a any
2a = 42 // int
3a = "hello" // string
4a = true // bool
5a = 3.14 // float64
Ainsi, le type any permet de contenir des valeurs de types variés, favorisant l'écriture de code flexible. Cependant, il convient de prêter attention à plusieurs points lors de l'utilisation du type any.
Type Assertion
Pour utiliser la valeur réelle d'une variable de type any, il est nécessaire d'employer une assertion de type (type assertion). L'assertion de type informe le compilateur que la variable de type any est d'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'était pas de type string, ok serait false, ce qui permettrait d'éviter que le programme ne panique.
Type Switch
Le type switch permet d'exécuter des actions différentes en fonction du type réel d'une variable any. Cela s'avère utile lorsque plusieurs types doivent être traités.
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 le type et la valeur d'une variable any à l'exécution. Ceci est particulièrement utile pour gérer 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 est utilisé pour vérifier le type et la valeur de a à l'exécution.
EFace
Dans le langage Go, les interfaces se divisent en deux formes principales : EFace (Empty Interface) et IFace (Interface with Methods). Parmi celles-ci, l'EFace correspond au concept du type any que nous avons abordé jusqu'à présent.
Structure
L'EFace étant une structure spéciale qui n'existe que dans le runtime du langage Go, nous allons en créer une imitation (mimic) pour la traiter ici.
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}
L'EFace est dotée de deux champs :
Type: un pointeur contenant les métadonnées du type réel de la valeur.Data: un pointeur contenant la valeur réelle.
De plus, l'EFace gère en interne les informations de type via la structure runtimeType. Cette structure inclut diverses métadonnées telles que la taille du type, son alignement et sa valeur de hachage.
Les champs sur lesquels nous devons nous concentrer sont Kind_, Hash et Equal. Kind_ indique la catégorie du type, tandis que Hash et Equal sont utilisés pour comparer les types et calculer les valeurs de hachage dans les tables de hachage.
Encapsulation
Nous allons maintenant encapsuler cette EFace afin de pouvoir utiliser la "magie noire" plus facilement.
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 récupère les informations de type pour les renvoyer sous forme de structure TypeInfo. Désormais, cette TypeInfo permet d'accéder facilement à la valeur de hachage, à la catégorie et à la fonction de comparaison du type.
Utilisation
L'utilisation est véritablement 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 cet exemple, des valeurs entières et à virgule flottante sont transmises à la fonction GetTypeInfo pour afficher leurs informations de type respectives, puis la fonction Equal est utilisée pour comparer les valeurs. Cela permet d'obtenir des informations de type et de comparer les types plus rapidement.
De plus, cette approche peut être appliquée de manière identique aux structures, aux pointeurs et aux interfaces, et elle peut également résoudre le problème fréquent du nil typé (typed nil). Bien que Hash soit alloué si un type est présent, This sera 0 si nil.
Conclusion
L'EFace joue actuellement un rôle crucial dans la gestion du type any en Go. Elle permet de traiter des valeurs de divers types de manière flexible et rapide, tout en offrant la possibilité d'obtenir et d'exploiter des informations de type détaillées. Cependant, il convient de noter qu'il s'agit d'une fonctionnalité masquée au sein du runtime, susceptible d'être affectée par des modifications des spécifications du langage.