Any n'est pas Any, mais Any est Any
Any
En langage Go, le type any est un alias pour l'interface magique interface{}
, capable de contenir tous les types. Autrement dit, tout 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 d'écrire du code flexible en acceptant des valeurs de divers types. Cependant, l'utilisation du type any requiert quelques précautions.
Type Assertion
Pour utiliser la valeur réelle d'une variable de type any, il est nécessaire d'employer une "type assertion". Une "type assertion" 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 un "panic" du programme.
Type Switch
Le "type switch" permet d'exécuter des actions différentes en fonction du type réel d'une variable de type any. Cette fonctionnalité est utile lors de la gestion de 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 du langage Go permet d'examiner et de manipuler dynamiquement le type et la valeur d'une variable de type any à l'exécution. Cette capacité est précieuse lors de la gestion de 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, EFace correspond au concept du type any que nous avons examiné jusqu'à présent.
Structure
EFace est une structure spéciale qui n'existe qu'au sein de l'environnement d'exécution du langage Go ; nous allons donc en créer une mimique pour cette discussion.
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
: un pointeur contenant les métadonnées sur le type réel de la valeur.Data
: un pointeur contenant la valeur réelle.
De plus, 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, l'alignement et la valeur de hachage.
Les champs qui méritent notre attention ici 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.
Emballage
Nous allons maintenant envelopper cet EFace pour le rendre plus facile à utiliser pour nos "magies noires".
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, en extrait la structure EFace interne, puis récupère les informations de type qu'elle contient pour les renvoyer sous forme de structure TypeInfo
. Il est désormais facile d'accéder à la valeur de hachage, à la catégorie et à la fonction de comparaison du type en utilisant cette TypeInfo
.
Utilisation
L'utilisation est très simple. Il suffit de passer la 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, et 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 méthode peut être utilisée de manière identique pour les structures, les pointeurs et les interfaces, et elle peut également résoudre le problème fréquent du nil typé ("typed nil"). En effet, bien que Hash
soit alloué si un type est présent, This
sera 0 si c'est nil.
Conclusion
EFace joue actuellement un rôle crucial dans la gestion du type any en langage Go. Il permet de traiter les valeurs de divers types de manière flexible et rapide, tout en obtenant et en exploitant des informations de type détaillées. Cependant, il convient de noter que cette fonctionnalité est une capacité cachée au sein de l'environnement d'exécution et qu'elle pourrait être affectée par des modifications des spécifications du langage.