Reflexión
La reflexión es un mecanismo que permite inspeccionar la estructura del lenguaje en tiempo de ejecución. Puede manejar problemas de manera flexible, pero también tiene desventajas obvias, como problemas de rendimiento. En Go, la reflexión está estrechamente relacionada con interface{}. En gran medida, dondequiera que aparezca interface{}, habrá reflexión. La API de reflexión en Go es proporcionada por el paquete estándar reflect.
Interfaces
Antes de comenzar, echemos un vistazo simple a dos interfaces en el paquete runtime. En Go, las interfaces son esencialmente estructuras. Go divide las interfaces en dos categorías principales en tiempo de ejecución: interfaces sin conjunto de métodos e interfaces con conjunto de métodos. Para las interfaces que contienen un conjunto de métodos, se representan en tiempo de ejecución por la siguiente estructura iface:
type iface struct {
tab *itab // contiene tipo de datos, tipo de interfaz, conjunto de métodos, etc.
data unsafe.Pointer // puntero al valor
}Para las interfaces sin conjunto de métodos, se representan en tiempo de ejecución por la estructura eface, como sigue:
type eface struct {
_type *_type // tipo
data unsafe.Pointer // puntero al valor
}Estas dos estructuras tienen sus tipos de estructura correspondientes en el paquete reflect. iface corresponde a nonEmptyInterface:
type nonEmptyInterface struct {
itab *struct {
ityp *rtype // tipo de interfaz estática
typ *rtype // tipo concreto dinámico
hash uint32 // hash del tipo
_ [4]byte
fun [100000]unsafe.Pointer // conjunto de métodos
}
word unsafe.Pointer // puntero al valor
}Y eface corresponde a emptyInterface:
type emptyInterface struct {
typ *rtype // tipo concreto dinámico
word unsafe.Pointer // valor que apunta al puntero
}Para estos dos tipos, la definición oficial es muy clara:
nonEmptyInterface: nonEmptyInterface is the header for an interface value with methodsemptyInterface: emptyInterface is the header for an interface{} value
El término "tipo concreto dinámico" mencionado anteriormente, en inglés dynamic concrete type. Primero, Go es un lenguaje de tipado estático al 100%. El término "estático" se refleja en que el tipo de interfaz abstracta manifestada externamente es invariable, mientras que "dinámico" significa que el tipo de implementación concreta almacenada en la interfaz puede cambiar. Hasta aquí, entender el principio simple de las interfaces es suficiente para continuar aprendiendo sobre reflexión.
Puente
En el paquete reflect, hay un tipo de interfaz reflect.Type para representar tipos en Go, y un tipo de estructura reflect.Value para representar valores en Go:
type Type interface {
...
Name() string
PkgPath() string
Size() uintptr
String() string
Kind() Kind
...
}
type Value struct {
typ *rtype
ptr unsafe.Pointer
flag
}El código anterior omite muchos detalles. Por ahora, solo necesitas conocer la existencia de estos dos tipos. Todas las operaciones relacionadas con la reflexión en Go se basan en estos dos tipos. El paquete reflect proporciona dos funciones para convertir tipos de Go a los dos tipos mencionados anteriormente para realizar operaciones de reflexión: la función reflect.TypeOf:
func TypeOf(i any) TypeY la función reflect.ValueOf:
func ValueOf(i any) ValueComo puedes ver, los parámetros de ambas funciones son del tipo any, que es un alias de interface{}. Si deseas realizar operaciones de reflexión, primero debes convertir el tipo a interface{}. Esta es la razón por la que se mencionó anteriormente que la reflexión no puede existir sin la interfaz vacía. Sin ser demasiado estricto, la interfaz vacía es el puente que conecta el sistema de tipos de Go con la reflexión. La siguiente imagen describe vívidamente este proceso.

TIP
En lo sucesivo, por conveniencia, usaremos uniformemente el alias any en lugar de interface{}.
Núcleo
En Go hay tres leyes clásicas de reflexión que, combinadas con el contenido anterior, son muy fáciles de entender:
- La reflexión puede convertir una variable de tipo
interface{}en un objeto de reflexión. - La reflexión puede restaurar un objeto de reflexión a una variable de tipo
interface{}. - Para modificar un objeto de reflexión, su valor debe ser configurable.
Estas tres leyes son el núcleo de la reflexión en Go. Cuando necesitas acceder a información relacionada con el tipo, necesitas usar reflect.TypeOf. Cuando necesitas modificar un valor de reflexión, necesitas usar reflect.ValueOf.
Tipos
reflect.Type representa los tipos en Go. Puedes convertir una variable a reflect.Type usando la función reflect.TypeOf(). El ejemplo de código es el siguiente:
func main() {
str := "hello world!"
reflectType := reflect.TypeOf(str)
fmt.Println(reflectType)
}El resultado de la salida es:
stringKind
Para Type, Go usa internamente reflect.Kind para representar los tipos básicos en Go. Esencialmente es un entero sin signo uint.
type Kind uintEl paquete reflect enumera todos los tipos básicos en Go usando Kind, como se muestra a continuación:
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
)El tipo Kind solo implementa el método String() de la interfaz Stringer. Este tipo tiene solo este método. El valor de retorno del método String() proviene de un slice interno, como se muestra a continuación. Esta sintaxis a primera vista parece un map, pero en realidad es una sintaxis especial en Go: sintaxis de inicialización de índice (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
}A través de Kind, puedes saber qué tipo básico almacena realmente la interfaz vacía. Por ejemplo:
func main() {
// Declarar una variable de tipo any
var eface any
// Asignar valor
eface = 100
// Obtener su tipo a través del método Kind
fmt.Println(reflect.TypeOf(eface).Kind())
}Resultado de la salida:
intElem
type Type interface{
Elem() Type
}Usando el método Type.Elem(), puedes determinar el tipo de elemento almacenado en una estructura de datos de tipo any. Los tipos de parámetros subyacentes aceptados deben ser uno de los siguientes: puntero, slice, array, canal o mapa. De lo contrario, causará panic. A continuación se muestra un ejemplo de código:
func main() {
var eface any
eface = map[string]int{}
rType := reflect.TypeOf(eface)
// key() devuelve el tipo de reflexión de la clave del map
fmt.Println(rType.Key().Kind())
fmt.Println(rType.Elem().Kind())
}La salida es:
string
intUn puntero también puede entenderse como un contenedor. Para un puntero, usar Elem() obtendrá el tipo de reflexión del elemento al que apunta. El ejemplo de código es el siguiente:
func main() {
var eface any
// Asignar puntero
eface = new(strings.Builder)
rType := reflect.TypeOf(eface)
// Obtener el tipo de reflexión del elemento al que apunta el puntero
vType := rType.Elem()
// Imprimir la ruta del paquete
fmt.Println(vType.PkgPath())
// Imprimir su nombre
fmt.Println(vType.Name())
}strings
BuilderPara arrays, slices y canales, el uso es similar.
Size
type Type interface{
Size() uintptr
}A través del método Size, puedes obtener el tamaño en bytes ocupado por el tipo correspondiente. El ejemplo es el siguiente:
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())
}Los resultados de la salida son:
8
16
16
8
24TIP
Usar unsafe.Sizeof() puede lograr el mismo efecto.
Comparable
type Type interface{
Comparable() bool
}A través del método Comparable, puedes determinar si un tipo puede ser comparado. El ejemplo es el siguiente:
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())
}La salida es la siguiente:
true
true
false
trueImplements
type Type interface{
Implements(u Type) bool
}A través del método Implements, puedes determinar si un tipo implementa una interfaz determinada:
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))
}Resultado de la salida:
true
falseConvertibleTo
type Type interface{
ConvertibleTo(u Type) bool
}Usando el método ConvertibleTo, puedes determinar si un tipo puede ser convertido a otro tipo especificado:
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))
}Salida:
true
falseValores
reflect.Value representa el valor de la interfaz de reflexión. Puedes convertir una variable a reflect.Value usando la función reflect.ValueOf(). El ejemplo de código es el siguiente:
func main() {
str := "hello world!"
reflectValue := reflect.ValueOf(str)
fmt.Println(reflectValue)
}El resultado de la salida es:
hello world!Type
func (v Value) Type() TypeEl método Type puede obtener el tipo de un valor de reflexión:
func main() {
num := 114514
rValue := reflect.ValueOf(num)
fmt.Println(rValue.Type())
}Salida:
intElem
func (v Value) Elem() ValueObtiene el valor de reflexión del elemento de un valor de reflexión:
func main() {
num := new(int)
*num = 114514
// Tomemos un puntero como ejemplo
rValue := reflect.ValueOf(num).Elem()
fmt.Println(rValue.Interface())
}Salida:
114514Punteros
Hay dos formas de obtener un puntero de un valor de reflexión:
// Devuelve un valor de reflexión de puntero que representa la dirección de v
func (v Value) Addr() Value
// Devuelve un uintptr que apunta al valor original de v, equivalente a uintptr(Value.Addr().UnsafePointer())
func (v Value) UnsafeAddr() uintptr
// Devuelve un uintptr que apunta al valor original de v
// Solo cuando el Kind de v es Chan, Func, Map, Pointer, Slice, UnsafePointer, de lo contrario panic
func (v Value) Pointer() uintptr
// Devuelve un unsafe.Pointer que apunta al valor original de v
// Solo cuando el Kind de v es Chan, Func, Map, Pointer, Slice, UnsafePointer, de lo contrario panic
func (v Value) UnsafePointer() unsafe.PointerEl ejemplo es el siguiente:
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())
}Salida:
&num 0xc0000a6058
Addr 0xc0000a6058
UnsafeAddr 0xc0000a6058
Pointer 0xc0000a6058
UnsafePointer 0xc0000a6058TIP
fmt.Println obtendrá el tipo del parámetro mediante reflexión. Si es del tipo reflect.Value, llamará automáticamente a Value.Interface() para obtener su valor original.
Probemos de nuevo con un 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())
}Salida:
0xc00010e4b0
Addr &map[]
UnsafeAddr 0xc00010e4b0
Pointer 0xc00010e4b0
UnsafePointer 0xc00010e4b0Establecer valor
func (v Value) Set(x Value)Si deseas modificar un valor de reflexión mediante reflexión, entonces su valor debe ser direccionable. En este caso, debes modificar el valor del elemento a través de un puntero, en lugar de intentar modificar directamente el valor del elemento.
func main() {
// *int
num := new(int)
*num = 114514
rValue := reflect.ValueOf(num)
// Obtener el elemento al que apunta el puntero
ele := rValue.Elem()
fmt.Println(ele.Interface())
ele.SetInt(11)
fmt.Println(ele.Interface())
}La salida es la siguiente:
114514
11Obtener valor
func (v Value) Interface() (i any)A través del método Interface(), puedes obtener el valor original del valor de reflexión:
func main() {
var str string
str = "hello"
rValue := reflect.ValueOf(str)
if v, ok := rValue.Interface().(string); ok {
fmt.Println(v)
}
}Salida:
helloFunciones
A través de la reflexión, puedes obtener toda la información de una función y también puedes llamar funciones mediante reflexión.
Información
Obtén toda la información de una función a través del tipo de reflexión:
func Max(a, b int) int {
if a > b {
return a
}
return b
}
func main() {
rType := reflect.TypeOf(Max)
// Imprimir el nombre de la función. Las funciones literales no tienen nombre
fmt.Println(rType.Name())
// Imprimir la cantidad de parámetros y valores de retorno
fmt.Println(rType.NumIn(), rType.NumOut())
rParamType := rType.In(0)
// Imprimir el tipo del primer parámetro
fmt.Println(rParamType.Kind())
rResType := rType.Out(0)
// Imprimir el tipo del primer valor de retorno
fmt.Println(rResType.Kind())
}Salida:
2 1
int
intLlamada
Llamar a una función a través de un valor de reflexión:
func (v Value) Call(in []Value) []Valuefunc main() {
// Obtener el valor de reflexión de la función
rType := reflect.ValueOf(Max)
// Pasar el array de parámetros
rResValue := rType.Call([]reflect.Value{reflect.ValueOf(18), reflect.ValueOf(50)})
for _, value := range rResValue {
fmt.Println(value.Interface())
}
}Salida:
50Estructuras
Supongamos que tenemos la siguiente estructura:
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
}Acceder a campos
La estructura de reflect.StructField es la siguiente:
type StructField struct {
// Nombre del campo
Name string
// Ruta del paquete
PkgPath string
// Nombre del tipo
Type Type
// Tag
Tag StructTag
// Desplazamiento en bytes del campo
Offset uintptr
// Índice
Index []int
// Si es un campo anidado
Anonymous bool
}Hay dos formas de acceder a los campos de una estructura: una es a través del índice y la otra es a través del nombre.
type Type interface{
Field(i int) StructField
}El ejemplo de acceso a través del índice es el siguiente:
func main() {
rType := reflect.TypeOf(new(Person)).Elem()
// Imprimir la cantidad de campos de la estructura
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())
}
}Salida:
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)
}El ejemplo de acceso a través del nombre es el siguiente:
func main() {
rType := reflect.TypeOf(new(Person)).Elem()
// Imprimir la cantidad de campos de la estructura
fmt.Println(rType.NumField())
if field, ok := rType.FieldByName("money"); ok {
fmt.Println(field.Name, field.Type, field.IsExported())
}
}Salida:
4
money int falseModificar campos
Si deseas modificar el valor de un campo de estructura, debes pasar un puntero a la estructura. A continuación se muestra un ejemplo de modificación de un campo:
func main() {
// Pasar puntero
rValue := reflect.ValueOf(&Person{
Name: "",
Age: 0,
Address: "",
money: 0,
}).Elem()
// Obtener campo
name := rValue.FieldByName("Name")
// Modificar valor del campo
if (name != reflect.Value{}) { // Si devuelve reflect.Value{}, significa que el campo no existe
name.SetString("jack")
}
// Imprimir estructura
fmt.Println(rValue.Interface())
}Salida:
{jack 0 0}Para modificar campos privados de una estructura, se necesitan algunas operaciones adicionales, como sigue:
func main() {
// Pasar puntero
rValue := reflect.ValueOf(&Person{
Name: "",
Age: 0,
Address: "",
money: 0,
}).Elem()
// Obtener un campo privado
money := rValue.FieldByName("money")
// Modificar valor del campo
if (money != reflect.Value{}) {
// Construir un valor de reflexión de puntero que apunte al campo no exportado de la estructura
p := reflect.NewAt(money.Type(), money.Addr().UnsafePointer())
// Obtener el elemento al que apunta este puntero, es decir, el campo a modificar
field := p.Elem()
// Modificar valor
field.SetInt(164)
}
// Imprimir estructura
fmt.Printf("%+v\n", rValue.Interface())
}Acceder a Tag
Después de obtener StructField, puedes acceder directamente a su Tag:
// Si no existe, ok es false
func (tag StructTag) Lookup(key string) (value string, ok bool)
// Si no existe, devuelve una cadena vacía
func (tag StructTag) Get(key string) stringEl ejemplo es el siguiente:
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"))
}
}Salida:
name true
nameAcceder a métodos
Acceder a métodos es similar a acceder a campos, solo que las firmas de las funciones son ligeramente diferentes. La estructura de reflect.Method es la siguiente:
type Method struct {
// Nombre del método
Name string
// Ruta del paquete
PkgPath string
// Tipo del método
Type Type
// Función correspondiente al método, el primer parámetro es el receptor
Func Value
// Índice
Index int
}El ejemplo de acceso a información de métodos es el siguiente:
func main() {
// Obtener tipo de reflexión de estructura
rType := reflect.TypeOf(new(Person)).Elem()
// Imprimir cantidad de métodos
fmt.Println(rType.NumMethod())
// Iterar e imprimir información de métodos
for i := 0; i < rType.NumMethod(); i++ {
method := rType.Method(i)
fmt.Println(method.Index, method.Name, method.Type, method.IsExported())
}
}Salida:
1
0 Talk func(main.Person, string) string trueSi deseas obtener detalles sobre los parámetros y valores de retorno del método, puedes obtenerlos a través de Method.Func. El proceso es el mismo que para acceder a la información de funciones. Modifiquemos ligeramente el código anterior:
func main() {
// Obtener tipo de reflexión de estructura
rType := reflect.TypeOf(new(Person)).Elem()
// Imprimir cantidad de métodos
fmt.Println(rType.NumMethod())
// Iterar e imprimir información de métodos
for i := 0; i < rType.NumMethod(); i++ {
method := rType.Method(i)
fmt.Println(method.Index, method.Name, method.Type, method.IsExported())
fmt.Println("Parámetros del método")
for i := 0; i < method.Func.Type().NumIn(); i++ {
fmt.Println(method.Func.Type().In(i).String())
}
fmt.Println("Valores de retorno del método")
for i := 0; i < method.Func.Type().NumOut(); i++ {
fmt.Println(method.Func.Type().Out(i).String())
}
}
}Como puedes ver, el primer parámetro es main.Person, que es el tipo del receptor:
1
0 Talk func(main.Person, string) string true
Parámetros del método
main.Person
string
Valores de retorno del método
stringLlamar métodos
Llamar a un método es similar a llamar a una función, y no necesitas pasar manualmente el receptor. El ejemplo es el siguiente:
func main() {
// Obtener tipo de reflexión de estructura
rValue := reflect.ValueOf(new(Person)).Elem()
// Imprimir cantidad de métodos
fmt.Println(rValue.NumMethod())
// Iterar e imprimir información de métodos
talk := rValue.MethodByName("Talk")
if (talk != reflect.Value{}) {
// Llamar al método y obtener el valor de retorno
res := talk.Call([]reflect.Value{reflect.ValueOf("hello,reflect!")})
// Iterar e imprimir valores de retorno
for _, re := range res {
fmt.Println(re.Interface())
}
}
}Salida:
1
hello,reflect!Creación
A través de la reflexión, puedes construir nuevos valores. El paquete reflect proporciona diferentes funciones más convenientes según algunos tipos especiales.
Tipos básicos
// Devuelve un valor de reflexión de puntero que apunta al valor de reflexión
func New(typ Type) ValueTomemos string como ejemplo:
func main() {
rValue := reflect.New(reflect.TypeOf(*new(string)))
rValue.Elem().SetString("hello world!")
fmt.Println(rValue.Elem().Interface())
}hello world!Estructuras
La creación de estructuras también usa la función 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() {
// Crear valor de reflexión de estructura
rType := reflect.TypeOf(new(Person)).Elem()
person := reflect.New(rType).Elem()
fmt.Println(person.Interface())
}Salida:
{ 0 0}Slices
Crear slice mediante reflexión:
func MakeSlice(typ Type, len, cap int) Valuefunc main() {
// Crear valor de reflexión de slice
rValue := reflect.MakeSlice(reflect.TypeOf(*new([]int)), 10, 10)
// Iterar y asignar valores
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
Crear Map mediante reflexión:
func MakeMapWithSize(typ Type, n int) Valuefunc main() {
// Construir valor de reflexión de map
rValue := reflect.MakeMapWithSize(reflect.TypeOf(*new(map[string]int)), 10)
// Establecer valor
rValue.SetMapIndex(reflect.ValueOf("a"), reflect.ValueOf(1))
fmt.Println(rValue.Interface())
}map[a:1]Canales
Crear canal mediante reflexión:
func MakeChan(typ Type, buffer int) Valuefunc main() {
// Crear valor de reflexión de canal
makeChan := reflect.MakeChan(reflect.TypeOf(new(chan int)).Elem(), 0)
fmt.Println(makeChan.Interface())
}Funciones
Crear función mediante reflexión:
func MakeFunc(typ Type, fn func(args []Value) (results []Value)) Valuefunc main() {
// Pasar tipo envoltorio y cuerpo de función
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)})
}Salida:
func(int)
1024Igualdad completa
reflect.DeepEqual es una función proporcionada por el paquete de reflexión para determinar si dos variables son completamente iguales. La firma es la siguiente:
func DeepEqual(x, y any) boolEsta función maneja cada tipo básico. A continuación se muestran algunas formas de判断 de tipos:
- Arrays: cada elemento del array es completamente igual
- Slices: son completamente iguales cuando ambos son
nil, o cuando ambos no están vacíos, los elementos dentro del rango de longitud son completamente iguales - Estructuras: todos los campos son completamente iguales
- Maps: son completamente iguales cuando ambos son
nil. Cuando ninguno esnil, los valores mapeados por cada clave son completamente iguales - Punteros: apuntan al mismo elemento o los elementos apuntados son completamente iguales
- Interfaces: cuando los tipos concretos de las interfaces son completamente iguales
- Funciones: son completamente iguales solo cuando ambos son
nil, de lo contrario no son completamente iguales
A continuación se muestran algunos ejemplos:
Slices
func main() {
a := make([]int, 100)
b := make([]int, 100)
fmt.Println(reflect.DeepEqual(a, b))
}Salida:
trueEstructuras
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))
}Salida:
false
false
true