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 :
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 :
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 :
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 :
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 methodsemptyInterface: 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 :
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 :
func TypeOf(i any) TypeEt la fonction reflect.ValueOf :
func ValueOf(i any) ValueOn 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 :
- La réflexion peut convertir une variable de type
interface{}en objet de réflexion - La réflexion peut restaurer un objet de réflexion en variable de type
interface{} - 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 :
func main() {
str := "hello world!"
reflectType := reflect.TypeOf(str)
fmt.Println(reflectType)
}Le résultat est :
stringKind
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.
type Kind uintLe package reflect énumère tous les types de base en Go en utilisant Kind, comme suit :
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) :
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",
}type Type interface{
Kind() Kind
}Grâce à Kind, on peut savoir quel type de base est stocké dans l'interface vide. Par exemple :
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 :
intElem
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 :
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
intUn 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 :
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
BuilderPour les tableaux, slices et canaux, l'utilisation est similaire.
Size
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 :
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
24TIP
L'utilisation de unsafe.Sizeof() permet d'obtenir le même résultat.
Comparable
type Type interface{
Comparable() bool
}La méthode Comparable permet de déterminer si un type peut être comparé. Voici un exemple :
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
trueImplements
type Type interface{
Implements(u Type) bool
}La méthode Implements permet de déterminer si un type implémente une interface donnée :
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
falseConvertibleTo
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é :
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
falseValeur
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 :
func main() {
str := "hello world!"
reflectValue := reflect.ValueOf(str)
fmt.Println(reflectValue)
}Résultat :
hello world!Type
func (v Value) Type() TypeLa méthode Type permet d'obtenir le type d'une valeur de réflexion :
func main() {
num := 114514
rValue := reflect.ValueOf(num)
fmt.Println(rValue.Type())
}Résultat :
intElem
func (v Value) Elem() ValuePermet d'obtenir la valeur de réflexion de l'élément d'une valeur de réflexion :
func main() {
num := new(int)
*num = 114514
// Exemple avec un pointeur
rValue := reflect.ValueOf(num).Elem()
fmt.Println(rValue.Interface())
}Résultat :
114514Pointeurs
Il existe deux façons d'obtenir le pointeur d'une valeur de réflexion :
// 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.PointerExemple :
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 0xc0000a6058TIP
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 :
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 0xc00010e4b0Définir une valeur
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.
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
11Obtenir une valeur
func (v Value) Interface() (i any)La méthode Interface() permet d'obtenir la valeur originale d'une valeur de réflexion :
func main() {
var str string
str = "hello"
rValue := reflect.ValueOf(str)
if v, ok := rValue.Interface().(string); ok {
fmt.Println(v)
}
}Résultat :
helloFonction
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 :
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
intAppel
Appeler une fonction via une valeur de réflexion :
func (v Value) Call(in []Value) []Valuefunc 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 :
50Structure
Supposons la structure suivante :
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 :
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.
type Type interface{
Field(i int) StructField
}Exemple d'accès par index :
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 falsetype Type interface{
FieldByName(name string) (StructField, bool)
}Exemple d'accès par nom :
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 falseModifier 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 :
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 :
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 :
// 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) stringExemple :
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
nameAccé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 :
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 :
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 truePour 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 :
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
stringAppeler 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 :
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
// Retourne une valeur de réflexion de pointeur pointant vers la valeur de réflexion
func New(typ Type) ValueExemple avec string :
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 :
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 :
func MakeSlice(typ Type, len, cap int) Valuefunc 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 :
func MakeMapWithSize(typ Type, n int) Valuefunc 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 :
func MakeChan(typ Type, buffer int) Valuefunc 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 :
func MakeFunc(typ Type, fn func(args []Value) (results []Value)) Valuefunc 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 :
func DeepEqual(x, y any) boolCette 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'estnil, 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
func main() {
a := make([]int, 100)
b := make([]int, 100)
fmt.Println(reflect.DeepEqual(a, b))
}Résultat :
trueStructure
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