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() == nilnil == 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)

(https://dave.cheney.net/2017/08/09/typed-nils-in-go-2)