Go에서 nil 비교할 때 주의해야 하는 점
nil != nil
누구나 이 명제를 보면 항상 거짓이라고 생각할 것이다. 하지만 놀랍게도, Go에서는 참이 되는 경우
도 있다.
아래의 상황을 보자
package main
import (
"fmt"
)
type A struct {}
var condition = false
func billo() interface{} {
var a *A = nil
if condition {
a = &A{}
}
return a
}
func main() {
fmt.Println(billo() == nil)
}
이 경우에 출력값을 잠시 고민해보자.
많은 분들이 true
라고 생각하겠지만, 사실은 false
이다. 실행하기
billo()
를 수행한 결과값이 nil
이라, billo() == nil
은 nil == nil
인데, 왜 false
가 나오는 지 궁금할 것이다.
이 현상은 interface
의 특징에서 기인한다 1. interface
는 타입을 저장하는 T
와 값을 저장하는 V
두가지로 나누어 저장된다. 예를 들어, “hello” 라는 string value를 interface에 저장한다면, T
에는 string
이, V
에는 "hello"
가 들어가는 식이다.
이를 이해하고, 위의 함수를 보자. billo()
의 아웃풋으로 리턴되는 a
의 경우, 값은 nil
일 지라도, 타입은 *A
임을 알 수 있다. 비교대상이 되는 nil
의 경우에는 타입도 nil
, 값도 nil
이다.
실제 billo()
함수의 타입과 값을 출력해보면, reflect.ValueOf(billo())
는 <nil>
,
reflect.TypeOf(billo())
는 *main.A
임을 알 수 있다. 이런 값은 typed nil
이라고 불린다.
이제 Go언어 spec에서 비교를 정의한 부분을 보자. 2 interface와 non-interface를 비교한 부분은 아래처럼 정의되어 있다.
A value x of non-interface type X and a value t of interface type T are comparable when values of type X are comparable and X implements T. They are equal if t's dynamic type is identical to X and t's dynamic value is equal to x.
인터페이스의 내부에 저장된 타입과 실제 데이터의 타입까지 모두 같아야만 같다고 정의되어 있다. nil 인 경우는 위의 내용에서 명확하게 이해하기는 어려우나, 비교 기작을 살펴 보았을 때 우리의 상황에서는 데이터 타입이 다르기 때문에 false가 나오게 됨을 이해할 수 있다.
이런 실수는 interface를 이용하다 보면 꼭 만나게 되는 문제이기도 하다. 따라서 nil을 리턴해야 하는 상황일 때는 return nil
형태를 사용하여 오해소지를 없애는 편이 좋을 것 같다.
Reference:
(https://medium.com/golangspec/equality-in-golang-ff44da79b7f1)