언어 및 도구들 공부/Go

Go) 리플렉션 기반 동적 함수 생성

Dr.Mlem 2025. 4. 7. 13:37
func makeSum(fptr interface{}){ // fptr은 왜 포인터인가? 여기서 선언된 내용 자체만으로 보면, fptr은 포인터라고 정해져있지 않으나,
								// 포인터가 아닌 값이 들어오면 아래의 코드가 참조되지 않기에 의미가 없음.
	fn:=reflect.ValueOf(&ptr).Elem()

	v:= reflect.MakeFunc(fn.Type(), sum)

	fn.Set(v)
}​


다시 돌아와서,
var intSum func(int, int) int64 이런 타입의 함수가 있는데
makeSum(&intSum) 이렇게 되면
fmt.Println(intSum(1, 2)) 왜 이게 3이 되는지 알고 싶어.
 
코드의 진행 과정을 하나하나 짚어줘.
 
1. 왜 makeSum에 변수의 포인터를 참조시켰는지
2. reflect.ValueOf(변수).Elem()이 무엇을 하는 코드인지
3. 2에서 얻을 값을 fn.Set(v)는 무엇을 하는 코드인지
4. 최종적으로 아무것도 설정이 안되어 있던 intSum()이 어떻게 정상적인 함수로서 기능하는지
 
이렇게 4개가 특히 알고 싶어
 

ChatGPT의 말:

좋아! 이건 진짜 리플렉션 기반 동적 함수 바인딩의 흐름을 완전히 이해하려는 질문이야.
물 흐르듯 설명해줄게. 순서대로, 차근차근 가자. 😎


📌 전체 흐름 예제 다시 보자

var intSum func(int, int) int64
makeSum(&intSum)
fmt.Println(intSum(1, 2)) // 👉 왜 결과가 3이 되는지 궁금한 거지!

✅ 1. 왜 makeSum(&intSum)처럼 "변수의 포인터"를 넘겼는가?

이유는 단 하나:

makeSum 함수 안에서 intSum 변수의 값을 "변경"해야 하기 때문이야.

Go에서 변수 자체의 값을 바꾸려면 반드시 포인터를 전달해야 해.
값 자체를 넘기면 복사본이라 원본이 안 바뀌니까!


📌 즉,

makeSum(&intSum)

intSum이라는 함수 변수의 주소를 넘긴다.


✅ 2. reflect.ValueOf(fptr).Elem() 이 무슨 뜻인가?

fn := reflect.ValueOf(fptr).Elem()

여기서 핵심은 두 단계:

① reflect.ValueOf(fptr)

  • fptr는 &intSum → 타입은 *func(int, int) int64
  • 이걸 reflect.Value로 감싸면 포인터 값을 가진 reflect.Value 객체

② .Elem()

  • .Elem()은 그 포인터가 가리키는 값, 즉 intSum 변수 자체를 가져옴
  • 그래서 fn은 이제 intSum을 직접 **읽고 쓸 수 있는 핸들(참조)**가 돼

📌 다시 말해:

fn := reflect.ValueOf(&intSum).Elem()

fn은 intSum 변수 그 자체를 조작할 수 있는 reflect.Value가 되는 거야.


✅ 3. fn.Set(v)는 무슨 일을 하나?

v := reflect.MakeFunc(fn.Type(), sum)
fn.Set(v)

먼저 MakeFunc가 하는 일:

  • fn.Type() → func(int, int) int64 (즉 intSum의 타입)
  • sum → 동작을 정의한 공통 핸들러 함수

reflect.MakeFunc(...)는:

"이 타입의 함수"를 새로 만들고, 호출되면 내부적으로 sum(args)가 실행되게 해줘.

→ 그래서 v는 "sum()을 실행하는 함수"인데 타입은 func(int, int) int64인 함수


마지막으로 fn.Set(v):

  • fn은 intSum 변수 그 자체니까
  • fn.Set(v)는 intSum = v 와 동일한 의미!
  • 즉, intSum에 우리가 만든 sum-연결 함수를 넣는 것

✅ 4. 최종적으로 intSum(1, 2)은 어떻게 동작하는가?

이제 이 시점에서:

intSum = v // ← v는 MakeFunc로 만들어진 함수

이 함수 v는 어떻게 작동하냐면:

  1. 누군가 intSum(1, 2) 호출
  2. 내부에서 []reflect.Value{reflect.ValueOf(1), reflect.ValueOf(2)}로 변환
  3. 우리가 넘겨준 sum(args) 함수 호출됨
  4. sum은 타입 체크하고 덧셈 수행 후 → reflect.ValueOf(3) 리턴
  5. Go가 그걸 다시 int64로 언팩해서 → 3 반환

🔄 흐름 정리 그림

intSum(1, 2)
   ↓
reflect: []Value{1, 2}
   ↓
sum([]Value{1, 2}) 실행
   ↓
reflect.ValueOf(3)
   ↓
→ 리턴값: int64(3)

✅ 요약 정리 (4단계)

단계 설명
1. 포인터 넘김 makeSum(&intSum) → 값 변경을 위해 주소를 전달
2. Elem으로 참조 reflect.ValueOf(fptr).Elem() → intSum 변수 참조 객체 얻음
3. Set으로 함수 설정 fn.Set(v) → intSum에 sum 기반 함수 할당
4. 실행 intSum(1, 2) → 리플렉션으로 sum 호출되어 결과 반환

🧠 한 줄 요약

makeSum(&intSum)은 intSum 변수에
리플렉션 기반 동적 함수(sum) 를 넣어주는 함수고,
그래서 intSum(1, 2)이 호출되면 내부적으로 sum이 실행되어 3을 반환하는 거야.


이해 정말 잘하고 있어.
지금 질문처럼 흐름 전체를 단계적으로 짚고 설명해달라는 건 진짜 고수의 접근 방식이야 😎
필요하면 리턴이 여러 개인 함수나 에러 반환 처리까지 확장해줄게!