Skip to content

Réflexion

La réflexion est un mécanisme qui permet d'inspecter la structure du langage lui-même à l'exécution. Elle offre une grande flexibilité pour résoudre certains problèmes, mais présente également des inconvénients évidents, tels que des problèmes de performance. En Go, la réflexion est étroitement liée à interface{}. Dans une large mesure, partout où interface{} apparaît, il y a de la réflexion. L'API de réflexion en Go est fournie par le package standard reflect.

Interface

Avant de commencer, examinons brièvement les deux interfaces du package runtime. En Go, les interfaces sont essentiellement des structures. Go divise les interfaces en deux grandes catégories à l'exécution : les interfaces sans ensemble de méthodes et les interfaces avec ensemble de méthodes. Pour les interfaces contenant un ensemble de méthodes, elles sont représentées à l'exécution par la structure iface suivante :

go
type iface struct {
   tab  *itab // contient le type de données, le type d'interface, l'ensemble de méthodes, etc.
   data unsafe.Pointer // pointeur vers la valeur
}

Pour les interfaces sans ensemble de méthodes, elles sont représentées à l'exécution par la structure eface suivante :

go
type eface struct {
   _type *_type // type
   data  unsafe.Pointer // pointeur vers la valeur
}

Ces deux structures ont des types de structure correspondants dans le package reflect. iface correspond à nonEmptyInterface :

go
type nonEmptyInterface struct {
  itab *struct {
    ityp *rtype // type d'interface statique
    typ  *rtype // type concret dynamique
    hash uint32 // hachage de type
    _    [4]byte
    fun  [100000]unsafe.Pointer // ensemble de méthodes
  }
  word unsafe.Pointer // pointeur vers la valeur
}

Et eface correspond à emptyInterface :

go
type emptyInterface struct {
   typ  *rtype // type concret dynamique
   word unsafe.Pointer // valeur pointée par le pointeur
}

Pour ces deux types, la documentation officielle donne des définitions claires :

  • nonEmptyInterface : nonEmptyInterface is the header for an interface value with methods
  • emptyInterface : emptyInterface is the header for an interface{} value

Le terme "type concret dynamique" mentionné ci-dessus, en anglais dynamic concrete type, fait référence au fait que Go est un langage de typage statique à 100%. Le terme "statique" signifie que le type d'interface abstrait manifesté extérieurement est invariant, tandis que "dynamique" signifie que le type concret stocké en interne par l'interface peut varier. Ainsi, pour comprendre le principe simple des interfaces, ce qui précède suffit pour l'apprentissage ultérieur de la réflexion.

Pont

Dans le package reflect, il existe le type d'interface reflect.Type pour représenter les types en Go, et le type de structure reflect.Value pour représenter les valeurs en Go :

go
type Type interface {
    ...

    Name() string

    PkgPath() string

    Size() uintptr

    String() string

    Kind() Kind

    ...
}

type Value struct {

   typ *rtype

   ptr unsafe.Pointer

   flag

}

Le code ci-dessus omet de nombreux détails. Pour l'instant, il suffit de connaître l'existence de ces deux types. Toutes les opérations de réflexion en Go sont basées sur ces deux types. Le package reflect fournit deux fonctions pour convertir les types Go en ces deux types afin d'effectuer des opérations de réflexion : la fonction reflect.TypeOf :

go
func TypeOf(i any) Type

Et la fonction reflect.ValueOf :

go
func ValueOf(i any) Value

On peut voir que les paramètres des deux fonctions sont de type any, qui est un alias de interface{}. Pour effectuer des opérations de réflexion, il faut d'abord convertir le type en interface{}. C'est pourquoi il a été mentionné précédemment que la réflexion ne peut pas se passer de l'interface vide. De manière non rigoureuse, l'interface vide est le pont qui relie le système de types Go et la réflexion. Le schéma suivant décrit ce processus de manière imagée.

TIP

Dans la suite, pour simplifier, nous utiliserons uniformément l'alias any à la place de interface{}.

Principes de base

En Go, il existe trois lois classiques de la réflexion qui, combinées à ce qui a été expliqué ci-dessus, sont faciles à comprendre :

  1. La réflexion peut convertir une variable de type interface{} en objet de réflexion
  2. La réflexion peut restaurer un objet de réflexion en variable de type interface{}
  3. Pour modifier un objet de réflexion, sa valeur doit être modifiable

Ces trois lois constituent le cœur de la réflexion en Go. Lorsqu'il est nécessaire d'accéder aux informations de type, il faut utiliser reflect.TypeOf. Lorsqu'il est nécessaire de modifier une valeur de réflexion, il faut utiliser reflect.ValueOf.

Type

reflect.Type représente les types en Go. La fonction reflect.TypeOf() peut convertir une variable en reflect.Type. Voici un exemple de code :

go
func main() {
  str := "hello world!"
  reflectType := reflect.TypeOf(str)
  fmt.Println(reflectType)
}

Le résultat est :

string

Kind

Pour Type, Go utilise en interne reflect.Kind pour représenter les types de base en Go, qui est essentiellement un entier non signé uint.

go
type Kind uint

Le package reflect énumère tous les types de base en Go en utilisant Kind, comme suit :

go
const (
   Invalid Kind = iota
   Bool
   Int
   Int8
   Int16
   Int32
   Int64
   Uint
   Uint8
   Uint16
   Uint32
   Uint64
   Uintptr
   Float32
   Float64
   Complex64
   Complex128
   Array
   Chan
   Func
   Interface
   Map
   Pointer
   Slice
   String
   Struct
   UnsafePointer
)

Le type Kind implémente uniquement la méthode String() de l'interface Stringer. Ce type n'a que cette seule méthode. La valeur de retour de la méthode String() provient d'un slice interne, comme suit. Cette syntaxe ressemble à première vue à une map, mais il s'agit en fait d'une syntaxe spéciale en Go : la syntaxe d'initialisation par index (index expressions in slice literals) :

go
var kindNames = []string{
   Invalid:       "invalid",
   Bool:          "bool",
   Int:           "int",
   Int8:          "int8",
   Int16:         "int16",
   Int32:         "int32",
   Int64:         "int64",
   Uint:          "uint",
   Uint8:         "uint8",
   Uint16:        "uint16",
   Uint32:        "uint32",
   Uint64:        "uint64",
   Uintptr:       "uintptr",
   Float32:       "float32",
   Float64:       "float64",
   Complex64:     "complex64",
   Complex128:    "complex128",
   Array:         "array",
   Chan:          "chan",
   Func:          "func",
   Interface:     "interface",
   Map:           "map",
   Pointer:       "ptr",
   Slice:         "slice",
   String:        "string",
   Struct:        "struct",
   UnsafePointer: "unsafe.Pointer",
}
go
type Type interface{
    Kind() Kind
}

Grâce à Kind, on peut savoir quel type de base est stocké dans l'interface vide. Par exemple :

go
func main() {
    // Déclarer une variable de type any
  var eface any
    // Affecter une valeur
  eface = 100
    // Obtenir son type via la méthode Kind
  fmt.Println(reflect.TypeOf(eface).Kind())
}

Résultat :

int

Elem

go
type Type interface{
    Elem() Type
}

La méthode Type.Elem() permet de déterminer le type d'élément stocké dans une structure de données de type any. Les types de paramètres acceptés doivent être l'un des suivants : pointeur, slice, tableau, canal ou map, sinon cela provoquera un panic. Voici un exemple de code :

go
func main() {
  var eface any
  eface = map[string]int{}
  rType := reflect.TypeOf(eface)
    // key() retourne le type de réflexion de la clé de la map
  fmt.Println(rType.Key().Kind())
  fmt.Println(rType.Elem().Kind())
}

Résultat :

string
int

Un pointeur peut également être compris comme un conteneur. Pour un pointeur, l'utilisation de Elem() permet d'obtenir le type de réflexion de l'élément pointé. Voici un exemple de code :

go
func main() {
  var eface any
    // Affecter un pointeur
  eface = new(strings.Builder)
  rType := reflect.TypeOf(eface)
    // Obtenir le type de réflexion de l'élément pointé par le pointeur
  vType := rType.Elem()
    // Afficher le chemin du package
  fmt.Println(vType.PkgPath())
    // Afficher son nom
  fmt.Println(vType.Name())
}
strings
Builder

Pour les tableaux, slices et canaux, l'utilisation est similaire.

Size

go
type Type interface{
    Size() uintptr
}

La méthode Size permet d'obtenir la taille en octets occupée par le type correspondant. Voici un exemple :

go
func main() {
  fmt.Println(reflect.TypeOf(0).Size())
  fmt.Println(reflect.TypeOf("").Size())
  fmt.Println(reflect.TypeOf(complex(0, 0)).Size())
  fmt.Println(reflect.TypeOf(0.1).Size())
  fmt.Println(reflect.TypeOf([]string{}).Size())
}

Résultat :

8
16
16
8
24

TIP

L'utilisation de unsafe.Sizeof() permet d'obtenir le même résultat.

Comparable

go
type Type interface{
    Comparable() bool
}

La méthode Comparable permet de déterminer si un type peut être comparé. Voici un exemple :

go
func main() {
  fmt.Println(reflect.TypeOf("hello world!").Comparable())
  fmt.Println(reflect.TypeOf(1024).Comparable())
  fmt.Println(reflect.TypeOf([]int{}).Comparable())
  fmt.Println(reflect.TypeOf(struct{}{}).Comparable())
}

Résultat :

true
true
false
true

Implements

go
type Type interface{
    Implements(u Type) bool
}

La méthode Implements permet de déterminer si un type implémente une interface donnée :

go
type MyInterface interface {
  My() string
}

type MyStruct struct {
}

func (m MyStruct) My() string {
  return "my"
}

type HisStruct struct {
}

func (h HisStruct) String() string {
  return "his"
}

func main() {
  rIface := reflect.TypeOf(new(MyInterface)).Elem()
  fmt.Println(reflect.TypeOf(new(MyStruct)).Elem().Implements(rIface))
  fmt.Println(reflect.TypeOf(new(HisStruct)).Elem().Implements(rIface))
}

Résultat :

true
false

ConvertibleTo

go
type Type interface{
    ConvertibleTo(u Type) bool
}

La méthode ConvertibleTo permet de déterminer si un type peut être converti en un autre type spécifié :

go
type MyInterface interface {
  My() string
}

type MyStruct struct {
}

func (m MyStruct) My() string {
  return "my"
}

type HisStruct struct {
}

func (h HisStruct) String() string {
  return "his"
}

func main() {
  rIface := reflect.TypeOf(new(MyInterface)).Elem()
  fmt.Println(reflect.TypeOf(new(MyStruct)).Elem().ConvertibleTo(rIface))
  fmt.Println(reflect.TypeOf(new(HisStruct)).Elem().ConvertibleTo(rIface))
}

Résultat :

true
false

Valeur

reflect.Value représente la valeur d'une interface de réflexion. La fonction reflect.ValueOf() peut convertir une variable en reflect.Value. Voici un exemple de code :

go
func main() {
  str := "hello world!"
  reflectValue := reflect.ValueOf(str)
  fmt.Println(reflectValue)
}

Résultat :

hello world!

Type

go
func (v Value) Type() Type

La méthode Type permet d'obtenir le type d'une valeur de réflexion :

go
func main() {
   num := 114514
   rValue := reflect.ValueOf(num)
   fmt.Println(rValue.Type())
}

Résultat :

int

Elem

go
func (v Value) Elem() Value

Permet d'obtenir la valeur de réflexion de l'élément d'une valeur de réflexion :

go
func main() {
   num := new(int)
   *num = 114514
   // Exemple avec un pointeur
   rValue := reflect.ValueOf(num).Elem()
   fmt.Println(rValue.Interface())
}

Résultat :

114514

Pointeurs

Il existe deux façons d'obtenir le pointeur d'une valeur de réflexion :

go
// Retourne une valeur de réflexion de pointeur représentant l'adresse de v
func (v Value) Addr() Value

// Retourne un uintptr pointant vers la valeur originale de v, équivalent à uintptr(Value.Addr().UnsafePointer())
func (v Value) UnsafeAddr() uintptr

// Retourne un uintptr pointant vers la valeur originale de v
// Uniquement lorsque le Kind de v est Chan, Func, Map, Pointer, Slice, UnsafePointer, sinon panic
func (v Value) Pointer() uintptr

// Retourne un unsafe.Pointer pointant vers la valeur originale de v
// Uniquement lorsque le Kind de v est Chan, Func, Map, Pointer, Slice, UnsafePointer, sinon panic
func (v Value) UnsafePointer() unsafe.Pointer

Exemple :

go
func main() {
   num := 1024
   ele := reflect.ValueOf(&num).Elem()
   fmt.Println("&num", &num)
   fmt.Println("Addr", ele.Addr())
   fmt.Println("UnsafeAddr", unsafe.Pointer(ele.UnsafeAddr()))
   fmt.Println("Pointer", unsafe.Pointer(ele.Addr().Pointer()))
   fmt.Println("UnsafePointer", ele.Addr().UnsafePointer())
}

Résultat :

&num 0xc0000a6058
Addr 0xc0000a6058
UnsafeAddr 0xc0000a6058
Pointer 0xc0000a6058
UnsafePointer 0xc0000a6058

TIP

fmt.Println utilise la réflexion pour obtenir le type des paramètres. S'il s'agit d'un type reflect.Value, il appellera automatiquement Value.Interface() pour obtenir sa valeur originale.

Essayons avec une map :

go
func main() {
  dic := map[string]int{}
  ele := reflect.ValueOf(&dic).Elem()
  println(dic)
  fmt.Println("Addr", ele.Addr())
  fmt.Println("UnsafeAddr", *(*unsafe.Pointer)(unsafe.Pointer(ele.UnsafeAddr())))
  fmt.Println("Pointer", unsafe.Pointer(ele.Pointer()))
  fmt.Println("UnsafePointer", ele.UnsafePointer())
}

Résultat :

0xc00010e4b0
Addr &map[]
UnsafeAddr 0xc00010e4b0
Pointer 0xc00010e4b0
UnsafePointer 0xc00010e4b0

Définir une valeur

go
func (v Value) Set(x Value)

Si l'on souhaite modifier une valeur de réflexion via la réflexion, sa valeur doit être adressable. Dans ce cas, il faut modifier la valeur de l'élément via un pointeur, plutôt que d'essayer de modifier directement la valeur de l'élément.

go
func main() {
   // *int
   num := new(int)
   *num = 114514
   rValue := reflect.ValueOf(num)
    // Obtenir l'élément pointé par le pointeur
   ele := rValue.Elem()
   fmt.Println(ele.Interface())
   ele.SetInt(11)
   fmt.Println(ele.Interface())
}

Résultat :

114514
11

Obtenir une valeur

go
func (v Value) Interface() (i any)

La méthode Interface() permet d'obtenir la valeur originale d'une valeur de réflexion :

go
func main() {
   var str string
   str = "hello"
   rValue := reflect.ValueOf(str)
   if v, ok := rValue.Interface().(string); ok {
      fmt.Println(v)
   }
}

Résultat :

hello

Fonction

La réflexion permet d'obtenir toutes les informations sur une fonction et également d'appeler une fonction via la réflexion.

Informations

Obtenir toutes les informations sur une fonction via le type de réflexion :

go
func Max(a, b int) int {
   if a > b {
      return a
   }
   return b
}

func main() {
   rType := reflect.TypeOf(Max)
   // Afficher le nom de la fonction. Les fonctions littérales n'ont pas de nom
   fmt.Println(rType.Name())
   // Afficher le nombre de paramètres et de valeurs de retour
   fmt.Println(rType.NumIn(), rType.NumOut())
   rParamType := rType.In(0)
   // Afficher le type du premier paramètre
   fmt.Println(rParamType.Kind())
   rResType := rType.Out(0)
   // Afficher le type de la première valeur de retour
   fmt.Println(rResType.Kind())
}

Résultat :


2 1
int
int

Appel

Appeler une fonction via une valeur de réflexion :

go
func (v Value) Call(in []Value) []Value
go
func main() {
   // Obtenir la valeur de réflexion de la fonction
   rType := reflect.ValueOf(Max)
   // Passer le tableau de paramètres
   rResValue := rType.Call([]reflect.Value{reflect.ValueOf(18), reflect.ValueOf(50)})
   for _, value := range rResValue {
      fmt.Println(value.Interface())
   }
}

Résultat :

50

Structure

Supposons la structure suivante :

go
type Person struct {
  Name    string `json:"name"`
  Age     int    `json:"age"`
  Address string `json:"address"`
  money   int
}

func (p Person) Talk(msg string) string {
  return msg
}

Accéder aux champs

La structure reflect.StructField est définie comme suit :

go
type StructField struct {
  // Nom du champ
  Name string
  // Chemin du package
  PkgPath string
  // Nom du type
  Type      Type
  // Tag
  Tag       StructTag
  // Décalage en octets du champ
  Offset    uintptr
  // Index
  Index     []int
  // Indique s'il s'agit d'un champ imbriqué
  Anonymous bool
}

Il existe deux méthodes pour accéder aux champs d'une structure : par index ou par nom.

go
type Type interface{
    Field(i int) StructField
}

Exemple d'accès par index :

go
func main() {
  rType := reflect.TypeOf(new(Person)).Elem()
  // Afficher le nombre de champs de la structure
  fmt.Println(rType.NumField())
  for i := 0; i < rType.NumField(); i++ {
    structField := rType.Field(i)
    fmt.Println(structField.Index, structField.Name, structField.Type, structField.Offset, structField.IsExported())
  }
}

Résultat :

4
[0] Name string 0 true
[1] Age int 16 true
[2] Address string 24 true
[3] money int 40 false
go
type Type interface{
    FieldByName(name string) (StructField, bool)
}

Exemple d'accès par nom :

go
func main() {
   rType := reflect.TypeOf(new(Person)).Elem()
   // Afficher le nombre de champs de la structure
   fmt.Println(rType.NumField())
   if field, ok := rType.FieldByName("money"); ok {
      fmt.Println(field.Name, field.Type, field.IsExported())
   }
}

Résultat :

4
money int false

Modifier un champ

Pour modifier la valeur d'un champ de structure, il faut passer un pointeur vers la structure. Voici un exemple de modification de champ :

go
func main() {
  // Passer un pointeur
  rValue := reflect.ValueOf(&Person{
    Name:    "",
    Age:     0,
    Address: "",
    money:   0,
  }).Elem()

  // Obtenir le champ
  name := rValue.FieldByName("Name")
  // Modifier la valeur du champ
  if (name != reflect.Value{}) { // Si reflect.Value{} est retourné, le champ n'existe pas
    name.SetString("jack")
  }
  // Afficher la structure
  fmt.Println(rValue.Interface())
}

Résultat :

{jack 0  0}

Pour modifier un champ privé d'une structure, des opérations supplémentaires sont nécessaires :

go
func main() {
  // Passer un pointeur
  rValue := reflect.ValueOf(&Person{
    Name:    "",
    Age:     0,
    Address: "",
    money:   0,
  }).Elem()

  // Obtenir un champ privé
  money := rValue.FieldByName("money")
  // Modifier la valeur du champ
  if (money != reflect.Value{}) {
    // Construire une valeur de réflexion de pointeur pointant vers le champ non exporté de la structure
    p := reflect.NewAt(money.Type(), money.Addr().UnsafePointer())
    // Obtenir l'élément pointé par ce pointeur, c'est-à-dire le champ à modifier
    field := p.Elem()
    // Modifier la valeur
    field.SetInt(164)
  }
  // Afficher la structure
  fmt.Printf("%+v\n", rValue.Interface())
}

Accéder aux Tags

Après avoir obtenu un StructField, on peut accéder directement à son Tag :

go
// Si n'existe pas, ok est false
func (tag StructTag) Lookup(key string) (value string, ok bool)

// Si n'existe pas, retourne une chaîne vide
func (tag StructTag) Get(key string) string

Exemple :

go
func main() {
   rType := reflect.TypeOf(new(Person)).Elem()
   name, ok := rType.FieldByName("Name")
   if ok {
      fmt.Println(name.Tag.Lookup("json"))
      fmt.Println(name.Tag.Get("json"))
   }
}

Résultat :

name true
name

Accéder aux méthodes

L'accès aux méthodes est similaire à l'accès aux champs, seules les signatures de fonction diffèrent légèrement. La structure reflect.Method est définie comme suit :

go
type Method struct {
  // Nom de la méthode
  Name string
  // Chemin du package
  PkgPath string
  // Type de la méthode
  Type  Type
  // Fonction correspondant à la méthode, le premier paramètre est le récepteur
  Func  Value
  // Index
  Index int
}

Exemple d'accès aux informations de méthode :

go
func main() {
  // Obtenir le type de réflexion de la structure
  rType := reflect.TypeOf(new(Person)).Elem()
  // Afficher le nombre de méthodes
  fmt.Println(rType.NumMethod())
  // Parcourir et afficher les informations de méthode
  for i := 0; i < rType.NumMethod(); i++ {
    method := rType.Method(i)
    fmt.Println(method.Index, method.Name, method.Type, method.IsExported())
  }
}

Résultat :

1
0 Talk func(main.Person, string) string true

Pour obtenir les détails des paramètres et des valeurs de retour d'une méthode, on peut utiliser Method.Func. Le processus est identique à l'accès aux informations de fonction. Modifions légèrement le code ci-dessus :

go
func main() {
  // Obtenir le type de réflexion de la structure
  rType := reflect.TypeOf(new(Person)).Elem()
  // Afficher le nombre de méthodes
  fmt.Println(rType.NumMethod())
  // Parcourir et afficher les informations de méthode
  for i := 0; i < rType.NumMethod(); i++ {
    method := rType.Method(i)
    fmt.Println(method.Index, method.Name, method.Type, method.IsExported())
    fmt.Println("Paramètres de la méthode")
    for i := 0; i < method.Func.Type().NumIn(); i++ {
      fmt.Println(method.Func.Type().In(i).String())
    }
    fmt.Println("Valeurs de retour de la méthode")
    for i := 0; i < method.Func.Type().NumOut(); i++ {
      fmt.Println(method.Func.Type().Out(i).String())
    }
  }
}

On peut voir que le premier paramètre est main.Person, c'est-à-dire le type du récepteur :

1
0 Talk func(main.Person, string) string true
Paramètres de la méthode
main.Person
string
Valeurs de retour de la méthode
string

Appeler une méthode

L'appel d'une méthode est similaire à l'appel d'une fonction, et il n'est pas nécessaire de passer manuellement le récepteur. Exemple :

go
func main() {
   // Obtenir le type de réflexion de la structure
   rValue := reflect.ValueOf(new(Person)).Elem()
   // Afficher le nombre de méthodes
   fmt.Println(rValue.NumMethod())
   // Parcourir et afficher les informations de méthode
   talk := rValue.MethodByName("Talk")
   if (talk != reflect.Value{}) {
      // Appeler la méthode et obtenir la valeur de retour
      res := talk.Call([]reflect.Value{reflect.ValueOf("hello,reflect!")})
      // Parcourir et afficher les valeurs de retour
      for _, re := range res {
         fmt.Println(re.Interface())
      }
   }
}

Résultat :

1
hello,reflect!

Création

La réflexion permet de construire de nouvelles valeurs. Le package reflect fournit également différentes fonctions plus pratiques pour certains types spéciaux.

Types de base

go
// Retourne une valeur de réflexion de pointeur pointant vers la valeur de réflexion
func New(typ Type) Value

Exemple avec string :

go
func main() {
   rValue := reflect.New(reflect.TypeOf(*new(string)))
   rValue.Elem().SetString("hello world!")
   fmt.Println(rValue.Elem().Interface())
}
hello world!

Structure

La création d'une structure utilise également la fonction reflect.New :

go
type Person struct {
   Name    string `json:"name"`
   Age     int    `json:"age"`
   Address string `json:"address"`
   money   int
}

func (p Person) Talk(msg string) string {
   return msg
}

func main() {
   // Créer une valeur de réflexion de structure
   rType := reflect.TypeOf(new(Person)).Elem()
   person := reflect.New(rType).Elem()
   fmt.Println(person.Interface())
}

Résultat :

{ 0  0}

Slice

Créer un slice via la réflexion :

go
func MakeSlice(typ Type, len, cap int) Value
go
func main() {
   // Créer une valeur de réflexion de slice
   rValue := reflect.MakeSlice(reflect.TypeOf(*new([]int)), 10, 10)
   // Parcourir et affecter des valeurs
   for i := 0; i < 10; i++ {
      rValue.Index(i).SetInt(int64(i))
   }
   fmt.Println(rValue.Interface())
}
[0 1 2 3 4 5 6 7 8 9]

Map

Créer une map via la réflexion :

go
func MakeMapWithSize(typ Type, n int) Value
go
func main() {
   // Construire une valeur de réflexion de map
   rValue := reflect.MakeMapWithSize(reflect.TypeOf(*new(map[string]int)), 10)
   // Définir une valeur
   rValue.SetMapIndex(reflect.ValueOf("a"), reflect.ValueOf(1))
   fmt.Println(rValue.Interface())
}
map[a:1]

Canal

Créer un canal via la réflexion :

go
func MakeChan(typ Type, buffer int) Value
go
func main() {
   // Créer une valeur de réflexion de canal
   makeChan := reflect.MakeChan(reflect.TypeOf(new(chan int)).Elem(), 0)
   fmt.Println(makeChan.Interface())
}

Fonction

Créer une fonction via la réflexion :

go
func MakeFunc(typ Type, fn func(args []Value) (results []Value)) Value
go
func main() {
    // Passer le type d'emballage et le corps de la fonction
  fn := reflect.MakeFunc(reflect.TypeOf(new(func(int))).Elem(), func(args []reflect.Value) (results []reflect.Value) {
    for _, arg := range args {
      fmt.Println(arg.Interface())
    }
    return nil
  })
  fmt.Println(fn.Type())
  fn.Call([]reflect.Value{reflect.ValueOf(1024)})
}

Résultat :

func(int)
1024

Égalité profonde

reflect.DeepEqual est une fonction fournie par le package de réflexion pour déterminer si deux variables sont complètement égales. Sa signature est la suivante :

go
func DeepEqual(x, y any) bool

Cette fonction gère chaque type de base. Voici quelques méthodes de jugement de type :

  • Tableau : chaque élément du tableau est complètement égal
  • Slice : complètement égaux lorsqu'ils sont tous les deux nil, ou lorsque ni l'un ni l'autre n'est vide, les éléments dans la plage de longueur sont complètement égaux
  • Structure : tous les champs sont complètement égaux
  • Map : complètement égales lorsqu'elles sont toutes les deux nil, ou lorsque ni l'une ni l'autre n'est nil, chaque valeur mappée par chaque clé est complètement égale
  • Pointeur : pointent vers le même élément ou les éléments pointés sont complètement égaux
  • Interface : lorsque les types concrets des interfaces sont complètement égaux
  • Fonction : complètement égales uniquement lorsque les deux sont nil, sinon pas complètement égales

Voici quelques exemples :

Slice

go
func main() {
   a := make([]int, 100)
   b := make([]int, 100)
   fmt.Println(reflect.DeepEqual(a, b))
}

Résultat :

true

Structure

go
func main() {
   mike := Person{
      Name:   "mike",
      Age:    39,
      Father: nil,
   }

   jack := Person{
      Name:   "jack",
      Age:    18,
      Father: &mike,
   }

   tom := Person{
      Name:   "tom",
      Age:    18,
      Father: &mike,
   }
   fmt.Println(reflect.DeepEqual(mike, jack))
   fmt.Println(reflect.DeepEqual(tom, jack))
   fmt.Println(reflect.DeepEqual(jack, jack))
}

Résultat :

false
false
true

Golang by www.golangdev.cn edit