Skip to content

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:

go
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:

go
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:

go
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:

go
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 methods
  • emptyInterface: 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:

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:

go
func TypeOf(i any) Type

Y la función reflect.ValueOf:

go
func ValueOf(i any) Value

Como 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:

  1. La reflexión puede convertir una variable de tipo interface{} en un objeto de reflexión.
  2. La reflexión puede restaurar un objeto de reflexión a una variable de tipo interface{}.
  3. 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:

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

El resultado de la salida es:

string

Kind

Para Type, Go usa internamente reflect.Kind para representar los tipos básicos en Go. Esencialmente es un entero sin signo uint.

go
type Kind uint

El paquete reflect enumera todos los tipos básicos en Go usando Kind, como se muestra a continuación:

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
)

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):

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
}

A través de Kind, puedes saber qué tipo básico almacena realmente la interfaz vacía. Por ejemplo:

go
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:

int

Elem

go
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:

go
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
int

Un 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:

go
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
Builder

Para arrays, slices y canales, el uso es similar.

Size

go
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:

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())
}

Los resultados de la salida son:

8
16
16
8
24

TIP

Usar unsafe.Sizeof() puede lograr el mismo efecto.

Comparable

go
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:

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())
}

La salida es la siguiente:

true
true
false
true

Implements

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

A través del método Implements, puedes determinar si un tipo implementa una interfaz determinada:

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))
}

Resultado de la salida:

true
false

ConvertibleTo

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

Usando el método ConvertibleTo, puedes determinar si un tipo puede ser convertido a otro tipo especificado:

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))
}

Salida:

true
false

Valores

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:

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

El resultado de la salida es:

hello world!

Type

go
func (v Value) Type() Type

El método Type puede obtener el tipo de un valor de reflexión:

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

Salida:

int

Elem

go
func (v Value) Elem() Value

Obtiene el valor de reflexión del elemento de un valor de reflexión:

go
func main() {
   num := new(int)
   *num = 114514
   // Tomemos un puntero como ejemplo
   rValue := reflect.ValueOf(num).Elem()
   fmt.Println(rValue.Interface())
}

Salida:

114514

Punteros

Hay dos formas de obtener un puntero de un valor de reflexión:

go
// 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.Pointer

El ejemplo es el siguiente:

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())
}

Salida:

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

TIP

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:

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())
}

Salida:

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

Establecer valor

go
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.

go
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
11

Obtener valor

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

A través del método Interface(), puedes obtener el valor original del valor de reflexión:

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

Salida:

hello

Funciones

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:

go
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
int

Llamada

Llamar a una función a través de un valor de reflexión:

go
func (v Value) Call(in []Value) []Value
go
func 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:

50

Estructuras

Supongamos que tenemos la siguiente estructura:

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
}

Acceder a campos

La estructura de reflect.StructField es la siguiente:

go
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.

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

El ejemplo de acceso a través del índice es el siguiente:

go
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 false
go
type Type interface{
    FieldByName(name string) (StructField, bool)
}

El ejemplo de acceso a través del nombre es el siguiente:

go
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 false

Modificar 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:

go
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:

go
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:

go
// 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) string

El ejemplo es el siguiente:

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"))
   }
}

Salida:

name true
name

Acceder 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:

go
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:

go
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 true

Si 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:

go
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
string

Llamar 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:

go
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

go
// Devuelve un valor de reflexión de puntero que apunta al valor de reflexión
func New(typ Type) Value

Tomemos string como ejemplo:

go
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:

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() {
   // 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:

go
func MakeSlice(typ Type, len, cap int) Value
go
func 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:

go
func MakeMapWithSize(typ Type, n int) Value
go
func 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:

go
func MakeChan(typ Type, buffer int) Value
go
func 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:

go
func MakeFunc(typ Type, fn func(args []Value) (results []Value)) Value
go
func 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)
1024

Igualdad 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:

go
func DeepEqual(x, y any) bool

Esta 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 es nil, 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

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

Salida:

true

Estructuras

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))
}

Salida:

false
false
true

Golang editado por www.golangdev.cn