Go의 ‘비교할 수 없는 유형’이 단순한 컴파일러 오류 그 이상인 이유
Go에서 일반 기능을 작업하는 동안 다음과 같은 오류가 발생했습니다. ‘유형 집합의 비교할 수 없는 유형’.
그로 인해 나는 더 깊이 파고들게 되었다. comparable
제약 조건 – Go의 제네릭에 깊은 영향을 미치는 겉으로는 간단해 보이는 기능입니다. 이것은 제네릭에 대한 첫 번째 브러시는 아니지만 종종 간과되는 중요한 뉘앙스를 강조했습니다. 기본적이지만 놀랍도록 직관적이며 불필요한 디버깅과 잠재적인 런타임 오류를 방지할 수 있는 Go 일반의 간과되는 측면입니다.
이 짧은 게시물을 통해 comparable
이것이 왜 유용한지, 더 깔끔하고 유형이 안전한 Go 코드에 어떻게 활용할 수 있는지에 대한 것입니다. 당신이 초보자이거나 초중급 Gopher라면, 이것은 당신을 위한 것입니다.
왜 comparable
?
Go의 유형 시스템은 간단하고 엄격하기로 유명합니다. 하지만 Go 1.18을 통해 제네릭이 등장하면서 재사용 가능하고 유형이 안전한 코드를 작성할 수 있는 새로운 도구가 생겼습니다. 소개와 함께 any
(별칭 interface{}
), 우리는 얻었습니다 comparable
. 그래서, 거래는 무엇입니까?
간단히 말해서, comparable
유형이 동등 비교를 지원하는지 확인하는 제약 조건입니다(==
그리고 !=
).
Go에서는 특정 유형만 본질적으로 비교할 수 있습니다. 여기에는 다음과 같은 기본 유형이 포함됩니다. int
, float64
그리고 string
하지만 슬라이스, 맵, 함수와 같은 유형은 제외됩니다. 이는 슬라이스와 맵이 참조 유형이기 때문입니다. 즉, 동등성은 해당 내용이 아니라 메모리 주소에 따라 결정될 수 있다는 의미입니다. 반면에 함수는 코드 블록에 대한 포인터를 나타내며 일반적으로 실용적인 목적으로는 비교할 수 없습니다.
시행함으로써 comparable
제약 조건에 따라 Go는 동등성 검사에 의존하는 일반 함수가 실수로 비교할 수 없는 유형을 허용하지 않도록 하여 디버그하기 어려운 런타임 오류를 방지합니다. 값 비교에 의존하는 일반 함수나 유형을 작성하기 위한 가드레일로 생각하세요.
슬라이스에 특정 값이 포함되어 있는지 확인하기 위해 일반 함수를 작성한다고 가정해 보겠습니다. 없이 comparable
Go에서는 본질적으로 비교할 수 없는 슬라이스나 맵과 같은 유형을 실수로 허용할 수도 있습니다.
결과는? 사용하려고 하면 컴파일러 오류가 발생합니다. ==
그 유형에.
와 함께 comparable
유효하고 비교 가능한 유형만 사용되도록 컴파일 타임에 적용할 수 있습니다. 이는 코드가 실행되기 전에 슬라이스 또는 맵 비교와 같은 런타임 오류의 전체 클래스를 제거하므로 매우 가치가 있습니다. 컴파일 타임 검사는 즉각적인 피드백을 제공하여 문제를 조기에 해결하고 함수가 처리하도록 설계된 유형으로 예측 가능하게 작동하도록 보장합니다.
단순성과 신뢰성을 강조하는 Go와 같은 언어에서 이러한 종류의 보호는 Go의 핵심 철학과 완벽하게 일치합니다. 컴파일러가 당신을 위해 어려운 일을 하도록 하세요.
예는 다음과 같습니다.
type Array[T comparable] struct {
data []T
}
func (a *Array[T]) Contains(value T) bool {
for _, v := range a.data {
if v == value {
return true
}
}
return false
}
func main() {
arr := Array[int]{data: []int{1, 2, 3, 4, 5}}
fmt.Println(arr.Contains(3)) // Output: true
fmt.Println(arr.Contains(10)) // Output: false
}
유형 매개변수로 슬라이스나 맵을 사용해 보십시오. 그러면 컴파일러가 즉시 트랙을 중지합니다. 이 가드레일은 comparable
비교 가능!
any
대 interface{}
:
Go 제네릭 생태계의 또 다른 플레이어는 any
이는 단순히 의 별칭입니다. interface{}
. 역사적으로, interface{}
모든 유형을 나타내는 Go의 초석이었지만 제네릭의 맥락에서 덜 직관적인 이름으로 인해 처음 사용하는 사람들이 혼동하는 경우가 많았습니다.
소개 any
Go 1.18에서는 보다 의미론적인 별칭을 제공하여 해당 유형이 ‘모든’ 값을 허용한다는 것을 즉시 명확하게 함으로써 이를 단순화하는 것을 목표로 했습니다. 기능적으로는 동일하지만 interface{}
, any
제네릭의 의도에 더 잘 부합하고 특히 제네릭 함수 시그니처에서 가독성을 향상시킵니다.
이것을 고려하십시오:
func PrintValues[T any](values []T) {
for _, v := range values {
fmt.Println(v)
}
}
여기서는 any
함수가 다음과 같이 작동한다는 것을 분명히 합니다. 어느 추가 제약 조건을 암시하지 않고 유형을 지정합니다.
이것을 다음과 비교해보세요:
func PrintValues[T interface{}](values []T) {
// Same functionality, but less intuitive for generics
}
하는 동안 interface{}
여전히 작동합니다. any
제네릭의 맥락에서 더 자연스럽게 느껴집니다. 이는 미묘한 변화이지만 코드에 더 접근하기 쉽게 만듭니다. 특히 새로 온 사람들에게는요.
실제 시나리오: 사용 시기 comparable
그리고 any
1. 중복 제거 comparable
다음은 사용에 대한 간단한 예입니다. comparable
슬라이스에서 중복 항목을 제거하려면 다음을 수행하세요.
func RemoveDuplicates[T comparable](input []T) []T {
seen := make(map[T]bool)
result := []T{}
for _, v := range input {
if !seen[v] {
seen[v] = true
result = append(result, v)
}
}
return result
}
func main() {
fmt.Println(RemoveDuplicates([]int{1, 2, 2, 3, 4, 4}))
// Output: [1 2 3 4]
fmt.Println(RemoveDuplicates([]string{"go", "go", "lang"}))
// Output: [go lang]
}
2. 유연한 유틸리티 any
사용 any
제약 조건에 의존하지 않는 기능의 경우. 예를 들어, 슬라이스의 모든 요소를 인쇄하는 간단한 유틸리티는 다음과 같습니다.
func PrintAll[T any](items []T) {
for _, item := range items {
fmt.Println(item)
}
}
func main() {
PrintAll([]int{1, 2, 3})
PrintAll([]string{"hello", "world"})
}
여기에는 제약조건이 필요하지 않으며 any
함수가 모든 유형에 열려 있음을 나타냅니다.
일반적인 함정
1. 비교할 수 없는 유형을 비교하려고 합니다.
이것을 시도해 본 적이 있다면:
arr := Array[[]int]{data: [][]int{{1, 2}, {3, 4}}}
fmt.Println(arr.Contains([]int{1, 2})) // Compiler error
슬라이스를 비교할 수 없기 때문에 컴파일 오류가 발생합니다. 해결 방법? 사용자 정의를 사용하여 슬라이스를 구조체로 감싸십시오. Equals
방법을 사용하거나 비교를 위해 해시 함수를 사용합니다.
2. 오용 any
과도한 사용을 피하세요 any
제약 조건으로 인해 코드가 더 안전해질 수 있습니다. 예를 들어, 슬라이스에서 최대값을 찾는 함수를 생각해 보세요.
func FindMax[T any](items []T) T {
max := items[0]
for _, item := range items {
if item > max {
max = item
}
}
return max
}
이 코드는 컴파일에 실패합니다. any
다음을 사용하여 항목을 비교할 수 있다는 의미는 아닙니다. >
. 대신에 T comparable
함수가 비교를 지원하는 유형만 안전하게 처리할 수 있는지 확인합니다.
func FindMax[T comparable](items []T) T {
max := items[0]
for _, item := range items {
if item > max {
max = item
}
}
return max
}
다음을 추가함으로써 comparable
제약 조건을 적용하면 컴파일 문제를 해결할 수 있을 뿐만 아니라 함수를 사용하는 모든 사람에게 함수의 의도와 요구 사항을 명확히 할 수 있습니다.
마무리
값을 검색하든, 슬라이스를 중복 제거하든, 자신만의 일반 유틸리티를 구축하든, comparable
예상치 못한 버그와 런타임 오류로부터 여러분을 구할 수 있습니다.
따라서 다음 번에 ‘비교할 수 없는 유형’ 오류 때문에 머리를 긁적일 때 기억하세요. 그것은 당신이 아니라 더 나은 디자인을 향해 당신을 밀어주는 Go입니다(항상 그랬듯이 :P).
아직도 확신이 없다면? 그것도 괜찮습니다. 언어의 특이한 점을 배우는 것은 개발자가 되는 즐거움(그리고 좌절감)의 일부입니다.
시간 내주셔서 감사합니다! 코드가 당신과 함께하길 바랍니다!
내 소셜 링크: 링크드인 | GitHub | 𝕏(이전 트위터) | 서브스택 | Dev.to | 레딧
Post Comment