any vs interface{} in Go: 차이점은 무엇일까요?
Ethan Miller
Product Engineer · Leapcell

any
vs interface{}
in Go: 차이점은 무엇일까요?
최근 팀 토론에서 누군가 "Go에서 any
와 interface{}
는 동일합니다."라고 주장했습니다. 기술적으로 그들은 옳습니다. Go의 공식 정의에 따르면 any
는 interface{}
의 별칭이 됩니다.
type any = interface{}
그들은 내부적으로 기능적으로 동일합니다. 그렇다면 Go 팀은 왜 any
를 도입했을까요? 의미론 및 가독성:
interface{}
: "제약 없는 동적 유형"을 나타냅니다 (JSON 파싱, 리플렉션). 동적성을 강조하며, 타입 단언이 필요합니다.any
: 제네릭을 위해 특별히 설계되었습니다. 제약 없는 타입 매개변수를 표시하여 타입 안전 일반성을 강조합니다. 타입이 설정되면 일관성을 유지합니다.
이러한 구별은 이전 버전과의 호환성을 유지하면서 제네릭 코드를 명확히 합니다.
Go 제네릭이 해결하는 문제
Go 1.18 이전 (제네릭이 등장하기 전)에는 개발자들이 서로 다른 타입으로 동일한 로직의 코드를 반복했습니다. 예: 숫자 슬라이스 합산:
// Sum []int64 func SumInts(numbers []int64) int64 { var s int64 for _, v := range numbers { s += v } return s } // Sum []float64 (동일한 로직, 다른 타입) func SumFloats(numbers []float64) float64 { var s float64 for _, v := range numbers { s += v } return s }
이는 DRY (Don't Repeat Yourself)를 위반하고 유지 관리 비용을 증가시킵니다.
Go 제네릭: 핵심 개념
Go 1.18은 3가지 주요 아이디어로 제네릭을 도입했습니다.
- 타입 매개변수: 함수/타입이 매개변수화된 타입을 사용하도록 합니다.
- 타입 제약 조건: 인터페이스를 통해 매개변수에 대한 유효한 타입을 정의합니다.
- 타입 추론: 호출 중에 타입을 자동으로 추론하여 코드를 단순화합니다.
제네릭으로 Sum 함수 리팩토링
// 제네릭 함수: int64 또는 float64에 대해 작동 func SumNumbers[T int64 | float64](numbers []T) T { var s T for _, v := range numbers { s += v } return s }
[T int64 | float64]
는 타입 매개변수를 선언합니다:T
는 타입 변수이고,int64 | float64
는 유니온 제약 조건입니다.- 호출 시 타입을 지정할 필요가 없습니다 (컴파일러가 추론합니다).
ints := []int64{1, 2, 3} floats := []float64{1.1, 2.2, 3.3} fmt.Println(SumNumbers(ints)) // 6 fmt.Println(SumNumbers(floats)) // 6.6
재사용 가능한 타입 제약 조건
복잡한/재사용 가능한 제약 조건의 경우 인터페이스로 정의합니다.
// 숫자에 대한 제약 조건 정의 type Number interface { int64 | float64 } // 사용자 정의 제약 조건으로 리팩토링 func SumNumbers[T Number](numbers []T) T { var s T for _, v := range numbers { s += v } return s }
이렇게 하면 가독성과 유지 관리성이 향상됩니다.
Go 제네릭 작동 방식: 성능 균형
Go의 제네릭은 GC Shape Monomorphization + Dictionaries (리플렉션 또는 v-테이블이 아님)라는 고유한 접근 방식 덕분에 효율적입니다.
-
GC Shape Monomorphization 컴파일러는 타입의 "GC shape" (크기, 정렬, 포인터 존재)를 기반으로 코드를 생성합니다. 예를 들면 다음과 같습니다.
int32
,uint32
,float32
는 동일한 shape (4바이트, 포인터 없음)를 공유합니다 → 코드를 재사용합니다.- 모든 포인터 타입 (
*int
,*string
)은 shape를 공유합니다 → 코드를 재사용합니다.
이렇게 하면 네이티브 성능과 일치하면서도 "코드 비대화"를 방지할 수 있습니다.
-
사전 기술 동일한 shape이지만 동작이 다른 타입 (예:
int
vsfloat32
덧셈)의 경우 컴파일러는 숨겨진 "사전"을 사용하여 타입별 정보 (메서드 주소, 연산 함수)를 전달합니다.
개발자를 위한 실제 영향
- 거의 네이티브 성능: 산술 연산은 비 제네릭 코드 속도와 일치합니다. 사전 기반 메서드 호출에 대한 최소 오버헤드 (인터페이스 호출과 유사).
- 제어된 바이너리 크기: 동일한 "GC shape" 타입에 대한 코드 재사용은 비대화를 방지합니다.
결론
any
와 interface{}
는 기술적으로 동일하지만, 그 의미는 Go의 타입 시스템 진화를 나타냅니다: 동적 타이핑을 위한 interface{}
, 제네릭을 위한 any
. 이를 이해하면 더 명확하고 관용적인 Go를 작성하는데 도움이 됩니다.
Leapcell: 최고의 서버리스 웹 호스팅
Go 서비스를 배포하기 위한 권장 플랫폼: Leapcell
🚀 좋아하는 언어로 빌드하세요
JavaScript, Python, Go 또는 Rust에서 손쉽게 개발하세요.
🌍 무제한 프로젝트를 무료로 배포하세요
사용하는 만큼만 지불하세요. 요청도, 요금도 없습니다.
⚡ 사용한 만큼 지불하고 숨겨진 비용은 없습니다
유휴 요금 없이 원활한 확장성만 제공합니다.
🔹 트위터 팔로우: @LeapcellHQ