Skip to content

リフレクション

リフレクションは、実行時に言語自体の構造を検査するメカニズムで、柔軟に問題に対処できますが、同時にパフォーマンス問題などの欠点も明らかです。Go では、リフレクションは interface{} と密接に関連しており、interface{} が現れる場所には必ずリフレクションが存在します。Go のリフレクション API は標準ライブラリ reflect パッケージによって提供されます。

インターフェース

始める前に、runtime パッケージ下の 2 つのインターフェースを簡単に理解しましょう。Go では、インターフェースは本質的に構造体で、Go は実行時にインターフェースを 2 つの大きなカテゴリに分類します。1 つはメソッドセットを持たないインターフェース、もう 1 つはメソッドセットを持つインターフェースです。メソッドセットを持つインターフェースの場合、実行時には以下の構造体 iface によって表されます。

go
type iface struct {
   tab  *itab // データ型,インターフェース型,メソッドセットなどを含む
   data unsafe.Pointer // 値へのポインタ
}

メソッドセットを持たないインターフェースの場合、実行時には eface 構造体によって表されます。

go
type eface struct {
   _type *_type // 型
   data  unsafe.Pointer // 値へのポインタ
}

これら 2 つの構造体は reflect パッケージ下に対応する構造体タイプがあり、ifacenonEmptyInterface に対応します。

go
type nonEmptyInterface struct {
  itab *struct {
    ityp *rtype // 静的インターフェース型
    typ  *rtype // 動的具象型
    hash uint32 // 型ハッシュ
    _    [4]byte
    fun  [100000]unsafe.Pointer // メソッドセット
  }
  word unsafe.Pointer // 値へのポインタ
}

efaceemptyInterface に対応します。

go
type emptyInterface struct {
   typ  *rtype // 動的具象型
   word unsafe.Pointer // ポインタを指す値
}

これら 2 つのタイプについて、公式は明確な定義を提供しています。

  • 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 パッケージ下には、Go の型を表す reflect.Type インターフェース型と、Go の値を表す reflect.Value 構造体型があります。

go
type Type interface {
    ...

    Name() string

  PkgPath() string

  Size() uintptr

  String() string

  Kind() Kind

    ...
}

type Value struct {

   typ *rtype

   ptr unsafe.Pointer

   flag

}

上記のコードでは多くの詳細を省略していますが、これら 2 つのタイプの存在を理解するだけで十分です。Go のすべてのリフレクション関連の操作はこれら 2 つのタイプに基づいており、reflect パッケージは Go の型を上記の 2 つの型に変換してリフレクション操作を行うための 2 つの関数を提供しています。それぞれ reflect.TypeOf 関数

go
func TypeOf(i any) Type

reflect.ValueOf 関数

go
func ValueOf(i any) Value

2 つの関数のパラメータ型はどちらも any で、interface{} のエイリアスです。リフレクション操作を実行したい場合は、まずその型を interface{} に変換する必要があります。これが前述の通り、リフレクションがあれば空インターフェースなしでは済まない理由です。厳密に言えば、空インターフェースは Go 型システムとリフレクションを接続する橋渡しです。以下の図はそのプロセスを形象的に表しています。

TIP

以下の文章では、便宜上、エイリアス any を使用して interface{} を置き換えます。

コア

Go には 3 つの古典的なリフレクションの法則があり、上記で説明した内容と組み合わせると理解しやすくなります。それぞれ以下の通りです。

  1. リフレクションは interface{} 型変数をリフレクションオブジェクトに変換できます。

  2. リフレクションはリフレクションオブジェクトを interface{} 型変数に復元できます。

  3. リフレクションオブジェクトを変更するには、その値は設定可能でなければなりません。

これら 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 を使用して Go の基本型を表し、本質的には符号なし整数 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 型は Stringer インターフェースの String() メソッドのみを実装しており、このタイプにはこのメソッドのみがあります。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

ポインタ

リフレクション値のポインタを取得する方法は 2 つあります。

go
// v のアドレスを表すポインタリフレクション値を返す
func (v Value) Addr() Value

// v の元の値への uintptr を返す。uintptr(Value.Addr().UnsafePointer()) と同等
func (v Value) UnsafeAddr() uintptr

// v の元の値への uintptr を返す
// v の Kind が Chan, Func, Map, Pointer, Slice, UnsafePointer の場合のみ使用可能、そうでない場合は panic
func (v Value) Pointer() uintptr

// v の元の値への unsafe.Pointer を返す
// v の Kind が 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       StructTag
  // フィールドのバイトオフセット
  Offset    uintptr
  // インデックス
  Index     []int
  // 埋め込みフィールドかどうか
  Anonymous bool
}

構造体フィールドにアクセスする方法は 2 つあり、1 つはインデックスを通じてアクセスし、もう 1 つは名前を通じてアクセスします。

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 は、2 つの変数が完全に等しいかどうかを判断するためにリフレクションパッケージが提供する関数で、シグネチャは以下の通りです。

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