이야기박스
Go. 왜 빈 struct{}를 context.Value()의 키로 사용할까? 본문
반응형
Go린이로써 가끔 git에서 context의 key로 빈 struct{}를 사용하는 것을 보고 궁금한 점이 늘 있었습니다. 그래서 오늘은 이 내용에 관한 포스팅을 작성해보려고 합니다.
type key struct{}
ctx = context.WithValue(ctx, key{}, "my value") // Set value
myValue, ok := ctx.Value(key{}).(string) // Get value
이번 포스팅은 아래의 문서를 가져와 작성하였습니다.
https://gist.github.com/ww9/4ad7b2ddfb94816a30dfdf2218e02f48
Sample Code
package main
import (
"fmt"
"unsafe"
)
type StructKey1 struct{}
type StructKey2 struct{}
type IntKey1 int
type IntKey2 int
// Why use empty struct{} and not int as context.Value() key types:
func main() {
// First of all, plain string keys look simple but they do not protect context
// values from being tampered by other packages:
// Also they cause allocation, so you probably don't want to use them.
compareKeys("dontUseString", "dontUseString") // true
// Now let's see how struct{} types behave.
// Same struct{} keys are always equal:
compareKeys(StructKey1{}, StructKey1{}) // true
// Different struct{} keys are never equal even if they appear to be of the same struct{} type:
compareKeys(StructKey1{}, StructKey2{}) // false
// This also applies to int keys. Same type means equal:
compareKeys(IntKey1(0), IntKey1(0)) // true
// And different keys are never equal even though they have 0 value:
compareKeys(IntKey1(0), IntKey2(0)) // false
// However, unlike struct{}, an int typed key allows for mistakes with the value:
compareKeys(IntKey1(0), IntKey1(1)) // false
// To add to why you shouldn't use int typed keys, when seeing that the package define a
// key as int, the user might be tempted to pass a primitive 0 instead. Which doesn't work:
compareKeys(IntKey1(0), 0) // false
// Whereas with struct{} there's no ambiguity or margin for missuse.
// So if for any reason your package wants to allow other packages to set/get a
// context.Value() that's specific to your package, either export a struct{} key
// like `type StructKey1 struct{}` or, even better, export methods that interact
// with these values but keep the key unexported:
// func WithUserIP(ctx context.Context, userIP net.IP) context.Context {
// return context.WithValue(ctx, userIPKey{}, userIP)
// }
// func UserIPFromContext(ctx context.Context) (net.IP, bool) {
// userIP, ok := ctx.Value(userIPKey{}).(net.IP)
// return userIP, ok
// }
}
func compareKeys(key1 interface{}, key2 interface{}) {
type ifaceHdr struct {
T unsafe.Pointer
V unsafe.Pointer
}
fmt.Println("\nkey1 == key2?", key1 == key2)
fmt.Printf("key1 %+v\n", *(*ifaceHdr)(unsafe.Pointer(&key1)))
fmt.Printf("key2 %+v\n", *(*ifaceHdr)(unsafe.Pointer(&key2)))
}
struct{} 비교
// Now let's see how struct{} types behave.
// Same struct{} keys are always equal:
compareKeys(StructKey1{}, StructKey1{}) // true
// Different struct{} keys are never equal even if they appear to be of the same struct{} type:
compareKeys(StructKey1{}, StructKey2{}) // false
StructKey1과 StructKey2는 모두 빈 struct{} 이지만, 이 둘을 비교하면 다르다고 나오게 됩니다.
IntKey 비교
// This also applies to int keys. Same type means equal:
compareKeys(IntKey1(0), IntKey1(0)) // true
// And different keys are never equal even though they have 0 value:
compareKeys(IntKey1(0), IntKey2(0)) // false
// However, unlike struct{}, an int typed key allows for mistakes with the value:
compareKeys(IntKey1(0), IntKey1(1)) // false
// To add to why you shouldn't use int typed keys, when seeing that the package define a
// key as int, the user might be tempted to pass a primitive 0 instead. Which doesn't work:
compareKeys(IntKey1(0), 0) // false
위와 마찬가지로 struct로 정의되게 되면, 내부 값이 같더라도 두 개의 key가 다르다는 결과값이 노출됩니다.
정리
`struct{}`를 키를 사용하는 것이, 다른 타입을 키로 이용하는 것보다 오사용할 가능성을 줄일 수 있어서 보편적으로 사용되는 것 같습니다.
추가로 내부에 어떠한 구성요소도 없기 때문에, 메모리 사용 측면에서도 보다 효율적이라고 하는 것 같습니다.
후기
앞으로 종종 Go린이 입장에서 이러한 포스팅을 하게 될 것 같습니다. 함께 공부해 나갑시다.
반응형
'Programming Language' 카테고리의 다른 글
심심하여 시작한, Mac에서 Flutter 설치 및 실행 (0) | 2023.06.10 |
---|---|
Go. gRPC 사용해보기 (0) | 2022.05.11 |
Go Test 간단하게 리뷰 (0) | 2022.02.10 |