Skip to content

Рефлексия

Рефлексия — это механизм, который позволяет проверять структуру самого языка во время выполнения. Он может гибко решать некоторые проблемы, но при этом имеет очевидные недостатки, такие как проблемы с производительностью. В Go рефлексия тесно связана с interface{}. В значительной степени, где бы ни появлялся interface{}, там будет и рефлексия. API рефлексии в Go предоставляется стандартным пакетом reflect.

Интерфейсы

Прежде чем начать, кратко рассмотрим два интерфейса в пакете runtime. В Go интерфейсы по сути являются структурами. Go во время выполнения разделяет интерфейсы на две большие категории: интерфейсы без набора методов и интерфейсы с набором методов. Для интерфейсов, содержащих набор методов, во время выполнения используется следующая структура iface:

go
type iface struct {
   tab  *itab // содержит тип данных, тип интерфейса, набор методов и т.д.
   data unsafe.Pointer // указатель на значение
}

Для интерфейсов без набора методов во время выполнения используется структура eface:

go
type eface struct {
   _type *_type // тип
   data  unsafe.Pointer // указатель на значение
}

Обе эти структуры имеют соответствующие типы структур в пакете reflect. iface соответствует nonEmptyInterface:

go
type nonEmptyInterface struct {
  itab *struct {
    ityp *rtype // статический тип интерфейса
    typ  *rtype // динамический конкретный тип
    hash uint32 // хэш типа
    _    [4]byte
    fun  [100000]unsafe.Pointer // набор методов
  }
  word unsafe.Pointer // указатель на значение
}

А eface соответствует emptyInterface:

go
type emptyInterface struct {
   typ  *rtype // динамический конкретный тип
   word unsafe.Pointer // значение указателя
}

Для этих двух типов официальная документация даёт чёткие определения:

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

Выше упоминался термин «динамический конкретный тип» (оригинал: dynamic concrete type). Прежде всего, Go — это на 100% статически типизированный язык. Слово «статический» проявляется в том, что абстрактный тип интерфейса, представленный внешне, неизменен, а «динамический» означает, что конкретный тип реализации, хранящийся внутри интерфейса, может изменяться. На этом достаточно знаний о принципах работы интерфейсов для последующего изучения рефлексии.

Мост

В пакете reflect есть тип интерфейса reflect.Type для представления типов в Go и тип структуры reflect.Value для представления значений в Go:

go
type Type interface {
    ...

    Name() string

  PkgPath() string

  Size() uintptr

  String() string

  Kind() Kind

    ...
}

type Value struct {

   typ *rtype

   ptr unsafe.Pointer

   flag

}

В приведённом коде опущено много деталей. Пока достаточно знать о существовании этих двух типов. Все операции рефлексии в Go основаны на этих двух типах. Пакет reflect предоставляет две функции для преобразования типов Go в вышеупомянутые два типа для выполнения операций рефлексии: функция reflect.TypeOf:

go
func TypeOf(i any) Type

И функция reflect.ValueOf:

go
func ValueOf(i any) Value

Можно видеть, что параметры обеих функций имеют тип any, то есть псевдоним interface{}. Если вы хотите выполнить операции рефлексии, нужно сначала преобразовать тип к interface{}. Именно поэтому ранее упоминалось, что рефлексия не обходится без пустого интерфейса. Не строго говоря, пустой интерфейс — это мост, соединяющий систему типов Go и рефлексию. Следующая схема наглядно описывает этот процесс.

TIP

В дальнейшем для удобства будем использовать псевдоним any вместо interface{}.

Ядро

В Go есть три классических закона рефлексии, которые,结合 с вышеизложенным, легко понять:

  1. Рефлексия может преобразовать переменную типа interface{} в объект рефлексии.

  2. Рефлексия может восстановить объект рефлексии в переменную типа interface{}.

  3. Чтобы изменить объект рефлексии, его значение должно быть устанавливаемым.

Эти три закона — ядро рефлексии в Go. Когда нужно получить доступ к информации о типе, используется reflect.TypeOf, а когда нужно изменить значение рефлексии, используется reflect.ValueOf.

Типы

reflect.Type представляет тип в Go. Использование функции reflect.TypeOf() позволяет преобразовать переменную в reflect.Type. Пример кода:

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

Вывод:

string

Kind

Для Type в Go внутренне используется reflect.Kind для представления базовых типов. По сути это беззнаковое целое число uint:

go
type Kind uint

Пакет reflect использует Kind для перечисления всех базовых типов в Go:

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
)

Тип Kind реализует только метод String() интерфейса Stringer. Этот тип имеет только один метод. Возвращаемое значение метода String() берётся из внутреннего slice:

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
}

С помощью Kind можно узнать, какой базовый тип хранится в пустом интерфейсе. Например:

go
func main() {
    // Объявляем переменную типа any
  var eface any
    // Присваиваем значение
  eface = 100
    // Получаем тип через метод Kind
  fmt.Println(reflect.TypeOf(eface).Kind())
}

Вывод:

int

Elem

go
type Type interface{
    Elem() Type
}

Используя метод Type.Elem(), можно определить тип элемента, хранящегося в структуре данных типа any. Допустимые типы параметров: указатель, срез, массив, канал, отображение. В противном случае произойдёт panic. Пример кода:

go
func main() {
  var eface any
  eface = map[string]int{}
  rType := reflect.TypeOf(eface)
    // Key() возвращает тип ключа map
  fmt.Println(rType.Key().Kind())
  fmt.Println(rType.Elem().Kind())
}

Вывод:

string
int

Указатель также можно понимать как контейнер. Для указателя использование Elem() вернёт тип элемента, на который он указывает. Пример кода:

go
func main() {
  var eface any
    // Присваиваем указатель
  eface = new(strings.Builder)
  rType := reflect.TypeOf(eface)
    // Получаем тип элемента, на который указывает указатель
  vType := rType.Elem()
    // Выводим путь к пакету
  fmt.Println(vType.PkgPath())
    // Выводим имя
  fmt.Println(vType.Name())
}
strings
Builder

Для массивов, срезов и каналов использование аналогично.

Size

go
type Type interface{
    Size() uintptr
}

Метод Size позволяет получить размер типа в байтах. Пример:

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

Вывод:

8
16
16
8
24

TIP

Использование unsafe.Sizeof() даёт тот же эффект.

Comparable

go
type Type interface{
    Comparable() bool
}

Метод Comparable позволяет определить, можно ли сравнивать тип. Пример:

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

Вывод:

true
true
false
true

Implements

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

Метод Implements позволяет определить, реализует ли тип определённый интерфейс:

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

Вывод:

true
false

ConvertibleTo

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

Метод ConvertibleTo позволяет определить, можно ли преобразовать один тип в другой указанный тип:

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

Вывод:

true
false

Значения

reflect.Value представляет значение рефлектируемого интерфейса. Использование функции reflect.ValueOf() позволяет преобразовать переменную в reflect.Value. Пример кода:

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

Вывод:

hello world!

Type

go
func (v Value) Type() Type

Метод Type позволяет получить тип рефлектируемого значения:

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

Вывод:

int

Elem

go
func (v Value) Elem() Value

Получение элемента рефлектируемого значения:

go
func main() {
   num := new(int)
   *num = 114514
   // На примере указателя
   rValue := reflect.ValueOf(num).Elem()
   fmt.Println(rValue.Interface())
}

Вывод:

114514

Указатели

Есть два способа получить указатель рефлектируемого значения:

go
// Возвращает указатель, представляющий адрес v
func (v Value) Addr() Value

// Возвращает uintptr, указывающий на исходное значение v, эквивалентно uintptr(Value.Addr().UnsafePointer())
func (v Value) UnsafeAddr() uintptr

// Возвращает uintptr, указывающий на исходное значение v
// Только когда Kind v равен Chan, Func, Map, Pointer, Slice, UnsafePointer, иначе panic
func (v Value) Pointer() uintptr

// Возвращает unsafe.Pointer, указывающий на исходное значение v
// Только когда Kind v равен Chan, Func, Map, Pointer, Slice, UnsafePointer, иначе panic
func (v Value) UnsafePointer() unsafe.Pointer

Пример:

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

Вывод:

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

TIP

fmt.Println рефлектирует тип параметра. Если тип reflect.Value, автоматически вызывается Value.Interface() для получения исходного значения.

Теперь с 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())
}

Вывод:

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

Установка значения

go
func (v Value) Set(x Value)

Если вы хотите изменить рефлектируемое значение через рефлексию, оно должно быть адресуемым. В этом случае следует изменять значение элемента через указатель, а не пытаться изменить значение элемента напрямую.

go
func main() {
   // *int
   num := new(int)
   *num = 114514
   rValue := reflect.ValueOf(num)
    // Получаем элемент, на который указывает указатель
   ele := rValue.Elem()
   fmt.Println(ele.Interface())
   ele.SetInt(11)
   fmt.Println(ele.Interface())
}

Вывод:

114514
11

Получение значения

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

Метод Interface() позволяет получить исходное значение рефлектируемого значения:

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

Вывод:

hello

Функции

Через рефлексию можно получить всю информацию о функции, а также вызвать функцию через рефлексию.

Информация

Получение всей информации о функции через тип рефлексии:

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

func main() {
   rType := reflect.TypeOf(Max)
   // Вывод имени функции. У литеральных функций нет имени.
   fmt.Println(rType.Name())
   // Вывод количества параметров и возвращаемых значений
   fmt.Println(rType.NumIn(), rType.NumOut())
   rParamType := rType.In(0)
   // Вывод типа первого параметра
   fmt.Println(rParamType.Kind())
   rResType := rType.Out(0)
   // Вывод типа первого возвращаемого значения
   fmt.Println(rResType.Kind())
}

Вывод:


2 1
int
int

Вызов

Вызов функции через значение рефлексии:

go
func (v Value) Call(in []Value) []Value
go
func main() {
   // Получаем рефлектируемое значение функции
   rType := reflect.ValueOf(Max)
   // Передаём массив параметров
   rResValue := rType.Call([]reflect.Value{reflect.ValueOf(18), reflect.ValueOf(50)})
   for _, value := range rResValue {
      fmt.Println(value.Interface())
   }
}

Вывод:

50

Структуры

Предположим, есть следующая структура:

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
}

Доступ к полям

Структура reflect.StructField выглядит следующим образом:

go
type StructField struct {
  // Имя поля
  Name string
  // Имя пакета
  PkgPath string
  // Имя типа
  Type      Type
  // Tag
  Tag       StructTag
  // Смещение поля в байтах
  Offset    uintptr
  // Индекс
  Index     []int
  // Является ли вложенным полем
  Anonymous bool
}

Есть два способа доступа к полям структуры: через индекс и через имя.

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

Пример доступа через индекс:

go
func main() {
  rType := reflect.TypeOf(new(Person)).Elem()
  // Вывод количества полей структуры
  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())
  }
}

Вывод:

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

Пример доступа через имя:

go
func main() {
   rType := reflect.TypeOf(new(Person)).Elem()
   // Вывод количества полей структуры
   fmt.Println(rType.NumField())
   if field, ok := rType.FieldByName("money"); ok {
      fmt.Println(field.Name, field.Type, field.IsExported())
   }
}

Вывод:

4
money int false

Изменение полей

Если нужно изменить значение поля структуры, необходимо передать указатель на структуру. Ниже пример изменения поля:

go
func main() {
  // Передаём указатель
  rValue := reflect.ValueOf(&Person{
    Name:    "",
    Age:     0,
    Address: "",
    money:   0,
  }).Elem()

  // Получаем поле
  name := rValue.FieldByName("Name")
  // Изменяем значение поля
  if (name != reflect.Value{}) { // Если возвращён reflect.Value{}, значит поле не существует
    name.SetString("jack")
  }
  // Вывод структуры
  fmt.Println(rValue.Interface())
}

Вывод:

{jack 0  0}

Для изменения приватного поля структуры требуются дополнительные операции:

go
func main() {
  // Передаём указатель
  rValue := reflect.ValueOf(&Person{
    Name:    "",
    Age:     0,
    Address: "",
    money:   0,
  }).Elem()

  // Получаем приватное поле
  money := rValue.FieldByName("money")
  // Изменяем значение поля
  if (money != reflect.Value{}) {
    // Создаём указатель на неэкспортируемое поле структуры
    p := reflect.NewAt(money.Type(), money.Addr().UnsafePointer())
    // Получаем элемент, на который указывает указатель, то есть поле для изменения
    field := p.Elem()
    // Изменяем значение
    field.SetInt(164)
  }
  // Вывод структуры
  fmt.Printf("%+v\n", rValue.Interface())
}

Доступ к Tag

Получив StructField, можно напрямую обратиться к его Tag:

go
// Если не существует, ok равно false
func (tag StructTag) Lookup(key string) (value string, ok bool)

// Если не существует, возвращает пустую строку
func (tag StructTag) Get(key string) string

Пример:

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

Вывод:

name true
name

Доступ к методам

Доступ к методам аналогичен доступу к полям, только сигнатуры функций немного отличаются. Структура reflect.Method:

go
type Method struct {
  // Имя метода
  Name string
  // Имя пакета
  PkgPath string
  // Тип метода
  Type  Type
  // Функция, соответствующая методу, первый параметр — получатель
  Func  Value
  // Индекс
  Index int
}

Пример доступа к информации о методе:

go
func main() {
  // Получаем тип рефлексии структуры
  rType := reflect.TypeOf(new(Person)).Elem()
  // Вывод количества методов
  fmt.Println(rType.NumMethod())
  // Перебираем и выводим информацию о методах
  for i := 0; i < rType.NumMethod(); i++ {
    method := rType.Method(i)
    fmt.Println(method.Index, method.Name, method.Type, method.IsExported())
  }
}

Вывод:

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

Если нужно получить детали параметров и возвращаемых значений метода, можно использовать Method.Func. Процесс аналогичен доступу к информации о функции. Немного изменим приведённый выше код:

go
func main() {
  // Получаем тип рефлексии структуры
  rType := reflect.TypeOf(new(Person)).Elem()
  // Вывод количества методов
  fmt.Println(rType.NumMethod())
  // Перебираем и выводим информацию о методах
  for i := 0; i < rType.NumMethod(); i++ {
    method := rType.Method(i)
    fmt.Println(method.Index, method.Name, method.Type, method.IsExported())
    fmt.Println("Параметры метода")
    for i := 0; i < method.Func.Type().NumIn(); i++ {
      fmt.Println(method.Func.Type().In(i).String())
    }
    fmt.Println("Возвращаемые значения метода")
    for i := 0; i < method.Func.Type().NumOut(); i++ {
      fmt.Println(method.Func.Type().Out(i).String())
    }
  }
}

Можно видеть, что первый параметр — main.Person, то есть тип получателя:

1
0 Talk func(main.Person, string) string true
Параметры метода
main.Person
string
Возвращаемые значения метода
string

Вызов метода

Вызов метода аналогичен вызову функции, причём не нужно вручную передавать получателя. Пример:

go
func main() {
   // Получаем значение рефлексии структуры
   rValue := reflect.ValueOf(new(Person)).Elem()
   // Вывод количества методов
   fmt.Println(rValue.NumMethod())
   // Перебираем и выводим информацию о методах
   talk := rValue.MethodByName("Talk")
   if (talk != reflect.Value{}) {
      // Вызываем метод и получаем возвращаемое значение
      res := talk.Call([]reflect.Value{reflect.ValueOf("hello,reflect!")})
      // Перебираем и выводим возвращаемые значения
      for _, re := range res {
         fmt.Println(re.Interface())
      }
   }
}

Вывод:

1
hello,reflect!

Создание

Через рефлексию можно создавать новые значения. Пакет reflect предоставляет различные удобные функции для специальных типов.

Базовые типы

go
// Возвращает указатель на рефлектируемое значение
func New(typ Type) Value

На примере string:

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

Структуры

Создание структуры также использует функцию 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() {
   // Создаём рефлектируемое значение структуры
   rType := reflect.TypeOf(new(Person)).Elem()
   person := reflect.New(rType).Elem()
   fmt.Println(person.Interface())
}

Вывод:

{ 0  0}

Срезы

Создание среза через рефлексию:

go
func MakeSlice(typ Type, len, cap int) Value
go
func main() {
   // Создаём рефлектируемое значение среза
   rValue := reflect.MakeSlice(reflect.TypeOf(*new([]int)), 10, 10)
   // Перебираем и присваиваем значения
   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

Создание Map через рефлексию:

go
func MakeMapWithSize(typ Type, n int) Value
go
func main() {
   // Создаём рефлектируемое значение map
   rValue := reflect.MakeMapWithSize(reflect.TypeOf(*new(map[string]int)), 10)
   // Устанавливаем значение
   rValue.SetMapIndex(reflect.ValueOf("a"), reflect.ValueOf(1))
   fmt.Println(rValue.Interface())
}
map[a:1]

Каналы

Создание канала через рефлексию:

go
func MakeChan(typ Type, buffer int) Value
go
func main() {
   // Создаём рефлектируемое значение канала
   makeChan := reflect.MakeChan(reflect.TypeOf(new(chan int)).Elem(), 0)
   fmt.Println(makeChan.Interface())
}

Функции

Создание функции через рефлексию:

go
func MakeFunc(typ Type, fn func(args []Value) (results []Value)) Value
go
func main() {
    // Передаём тип обёртки и тело функции
  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)})
}

Вывод:

func(int)
1024

Полное равенство

reflect.DeepEqual — функция, предоставляемая пакетом рефлексии для определения полного равенства двух переменных. Сигнатура:

go
func DeepEqual(x, y any) bool

Эта функция обрабатывает каждый базовый тип. Ниже приведены способы сравнения для некоторых типов:

  • Массивы: каждый элемент массивов полностью равен
  • Срезы: полностью равны, если оба nil, или если оба не пусты и элементы в пределах длины полностью равны
  • Структуры: все поля полностью равны
  • Отображения: полностью равны, если оба nil; если оба не nil, то значения, отображаемые каждым ключом, полностью равны
  • Указатели: указывают на один и тот же элемент или указывают на полностью равные элементы
  • Интерфейсы: конкретные типы интерфейсов полностью равны
  • Функции: полностью равны только если оба nil, иначе не равны

Ниже приведены примеры:

Срезы

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

Вывод:

true

Структуры

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

Вывод:

false
false
true

Golang by www.golangdev.cn edit