Skip to content

Commit 06c6788

Browse files
authored
Merge pull request #138 from gosuda/feature/eface
feat(blog): add eFace and any type explanation with examples
2 parents ed422bd + 7a74cd4 commit 06c6788

1 file changed

Lines changed: 167 additions & 0 deletions

File tree

root/blog/eface.md

Lines changed: 167 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,167 @@
1+
---
2+
author: snowmerak
3+
title: Any is not Any, but Any is Any
4+
language: ko
5+
---
6+
7+
## Any
8+
9+
Go 언어에서 any 타입은 모든 타입을 담을 수 있는 마법같은 인터페이스, `interface{}`의 별칭입니다. 즉, 어떤 타입이든 any 타입 변수에 할당할 수 있습니다.
10+
11+
```go
12+
var a any
13+
a = 42 // int
14+
a = "hello" // string
15+
a = true // bool
16+
a = 3.14 // float64
17+
```
18+
19+
이처럼 any 타입은 다양한 타입의 값을 담을 수 있어 유연한 코드를 작성할 수 있게 해줍니다. 하지만, any 타입을 사용할 때는 몇 가지 주의할 점이 있습니다.
20+
21+
### Type Assertion
22+
23+
any 타입 변수에서 실제 값을 사용하려면 타입 단언(type assertion)을 사용해야 합니다. 타입 단언은 any 타입 변수가 특정 타입임을 컴파일러에게 알려주는 역할을 합니다.
24+
25+
```go
26+
var a any = "hello"
27+
str, ok := a.(string)
28+
if ok {
29+
fmt.Println("String value:", str)
30+
} else {
31+
fmt.Println("Not a string")
32+
}
33+
```
34+
35+
위 예제에서 `a.(string)``a`가 string 타입임을 단언합니다. 만약 `a`가 string 타입이 아니라면, `ok`는 false가 되고, 프로그램이 패닉에 빠지는 것을 방지할 수 있습니다.
36+
37+
### Type Switch
38+
39+
타입 스위치(type switch)는 any 타입 변수의 실제 타입에 따라 다른 동작을 수행할 수 있게 해줍니다. 이는 여러 타입을 처리해야 할 때 유용합니다.
40+
41+
```go
42+
var a any = 42
43+
switch v := a.(type) {
44+
case int:
45+
fmt.Println("Integer:", v)
46+
case string:
47+
fmt.Println("String:", v)
48+
case bool:
49+
fmt.Println("Boolean:", v)
50+
default:
51+
fmt.Println("Unknown type")
52+
}
53+
```
54+
55+
위 예제에서 `a.(type)``a`의 실제 타입을 검사하고, 해당 타입에 맞는 케이스를 실행합니다.
56+
57+
### Reflection
58+
59+
Go 언어의 reflect 패키지를 사용하면 any 타입 변수의 타입과 값을 런타임에 동적으로 검사하고 조작할 수 있습니다. 이는 복잡한 데이터 구조를 다룰 때 유용합니다.
60+
61+
```go
62+
import (
63+
"fmt"
64+
"reflect"
65+
)
66+
67+
var a any = 3.14
68+
v := reflect.ValueOf(a)
69+
fmt.Println("Type:", v.Type())
70+
fmt.Println("Value:", v.Float())
71+
```
72+
73+
위 예제에서 reflect 패키지를 사용하여 `a`의 타입과 값을 런타임에 확인할 수 있습니다.
74+
75+
## EFace
76+
77+
Go 언어에서 인터페이스는 두 가지 주요 형태로 나뉩니다: EFace(Empty Interface)와 IFace(Interface with Methods)입니다. 이 중 EFace가 저희가 지금까지 다룬 any 타입과 동일한 개념입니다.
78+
79+
### 구조
80+
81+
EFace는 Go 언어의 런타임 내에서만 존재하는 특별한 구조체이기에 여기서는 미믹(mimic)을 만들어서 다루겠습니다.
82+
83+
```go
84+
type runtimeTypeMimic struct {
85+
Size_ uintptr
86+
PtrBytes uintptr // number of (prefix) bytes in the type that can contain pointers
87+
Hash uint32 // hash of type; avoids computation in hash tables
88+
TFlag uint8 // extra type information flags
89+
Align_ uint8 // alignment of variable with this type
90+
FieldAlign_ uint8 // alignment of struct field with this type
91+
Kind_ uint8 // enumeration for C
92+
Equal func(unsafe.Pointer, unsafe.Pointer) bool
93+
GCData *byte
94+
Str int32 // string form
95+
PtrToThis int32 // type for pointer to this type, may be zero
96+
}
97+
98+
type eFaceMimic struct {
99+
Type *runtimeTypeMimic
100+
Data unsafe.Pointer
101+
}
102+
```
103+
104+
EFace는 두 개의 필드를 가지고 있습니다:
105+
- `Type`: 값의 실제 타입에 대한 메타데이터를 담고 있는 포인터입니다.
106+
- `Data`: 실제 값을 담고 있는 포인터입니다.
107+
108+
그리고 EFace는 내부적으로 `runtimeType` 구조체를 통해 타입 정보를 관리합니다. 이 구조체는 타입의 크기, 정렬, 해시 값 등 다양한 메타데이터를 포함하고 있습니다.
109+
110+
이 안에서 저희가 주목해야 할 부분은 `Kind_``Hash`, `Equal` 필드입니다. `Kind_`는 타입의 종류를 나타내며, `Hash``Equal`은 해시 테이블에서 타입을 비교하고 해시 값을 계산하는 데 사용됩니다.
111+
112+
### 포장
113+
114+
이제 이 EFace를 래핑해서 좀 더 편하게 흑마술을 쓸 수 있게 만들어봅시다.
115+
116+
```go
117+
type TypeInfo struct {
118+
Hash uint32
119+
TFlag uint8
120+
Kind uint8
121+
Equal func(unsafe.Pointer, unsafe.Pointer) bool
122+
This unsafe.Pointer
123+
}
124+
125+
func GetTypeInfo(v any) TypeInfo {
126+
eface := *(*eFaceMimic)(unsafe.Pointer(&v))
127+
return TypeInfo{
128+
Hash: eface.Type.Hash,
129+
TFlag: eface.Type.TFlag,
130+
Kind: eface.Type.Kind_,
131+
Equal: eface.Type.Equal,
132+
This: eface.Data,
133+
}
134+
}
135+
```
136+
137+
위 함수는 any 타입 변수를 받아서 내부의 EFace 구조체를 추출하고, 그 안에서 타입 정보를 꺼내어 `TypeInfo` 구조체로 반환합니다. 이제 이 `TypeInfo`를 사용하여 타입의 해시 값, 종류, 비교 함수 등을 쉽게 접근할 수 있습니다.
138+
139+
### 활용
140+
141+
활용은 정말 쉽습니다. 그냥 `GetTypeInfo` 함수에 any 타입 변수를 넘겨주면 됩니다.
142+
143+
```go
144+
func main() {
145+
i := 42
146+
eface := GetTypeInfo(i)
147+
fmt.Printf("%+v\n", eface)
148+
149+
f := 3.14
150+
eface2 := GetTypeInfo(f)
151+
fmt.Printf("%+v\n", eface2)
152+
153+
// Compare the two values using the Equal function from the type info
154+
log.Println(eface.Equal(eface.This, eface.This))
155+
log.Println(eface2.Equal(eface2.This, eface2.This))
156+
log.Println(eface2.Equal(eface.This, eface2.This))
157+
log.Println(eface.Equal(eface2.This, eface.This))
158+
}
159+
```
160+
161+
위 예제에서는 정수와 부동 소수점 값을 `GetTypeInfo` 함수에 넘겨서 각각의 타입 정보를 출력하고, `Equal` 함수를 사용하여 값들을 비교합니다. 이를 통해 좀 더 빠르게 타입 정보를 얻고, 타입을 비교할 수 있습니다.
162+
163+
또한 이 방식은 구조체와 포인터, 인터페이스 등에도 동일하게 사용할 수 있으며, 적지 않게 발생하는 이슈인 타입 있는 nil(typed nil) 문제도 해결할 수 있습니다. `Hash`는 타입이 있으면 할당되어 있겠지만, `This`는 nil이면 0이기 때문입니다.
164+
165+
## 결론
166+
167+
EFace는 현재 Go 언어에서 any 타입을 다루는 데 있어 중요한 역할을 합니다. 이를 통해 다양한 타입의 값을 유연하고 빠르게 처리할 수 있으며, 타입 정보를 자세히 얻고 활용할 수 있습니다. 다만, 이는 런타임 내에 숨겨진 기능으로 언어 스펙 변경의 영향을 받을 수 있으므로 주의해야합니다.

0 commit comments

Comments
 (0)