Skip to content

리플렉션

리플렉션은 실행 시 언어 자체의 구조를 검사할 수 있는 메커니즘으로, 일부 문제에 유연하게 대응할 수 있게 해주지만 동시에 성능 문제와 같은 단점도 분명히 존재합니다. Go 에서 리플렉션은 interface{}와 밀접한 관련이 있으며, 크게 말해 interface{}가 나타나는 곳에는 리플렉션이 존재한다고 할 수 있습니다. Go 의 리플렉션 API 는 표준 라이브러리 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 패키지에서 각각 대응하는 구조체 타입이 있습니다. 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 // 포인터를 가리키는 값
}

이 두 타입에 대해 공식은 매우 명확한 정의를 내렸습니다.

  • 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

}

위 코드는 많은 세부사항을 생략했으며, 먼저 이 두 타입의 존재만 이해하면 됩니다. 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 를 사용하여 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

포인터

리플렉션 값의 포인터를 얻는 방법은 두 가지가 있습니다.

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