Go 인터페이스와 nil 비교 시 주의사항
문제 상황
에러 핸들링 로직에서 이상한 버그를 발견했다. 분명 nil을 반환했는데 if err != nil 체크에 걸리는 현상이었다.
func GetUser(id string) (*User, error) {
var dbErr *DatabaseError
// ... 로직
if someCondition {
return nil, dbErr // dbErr는 nil
}
return user, nil
}
// 호출부
user, err := GetUser("123")
if err != nil {
// dbErr가 nil인데도 여기로 진입함
log.Printf("error occurred: %v", err)
}
원인
Go의 인터페이스는 내부적으로 (type, value) 쌍으로 구성된다. *DatabaseError 타입의 nil 값을 error 인터페이스로 반환하면, 타입 정보는 *DatabaseError이고 값만 nil인 상태가 된다.
따라서 인터페이스 자체는 nil이 아니다. 인터페이스가 nil이려면 타입과 값이 모두 nil이어야 한다.
해결
구체 타입 대신 인터페이스 타입을 직접 사용하도록 수정했다.
func GetUser(id string) (*User, error) {
// var dbErr *DatabaseError (X)
var err error // (O)
if someCondition {
err = &DatabaseError{msg: "connection failed"}
return nil, err
}
return user, nil // err는 완전한 nil
}
또는 명시적으로 nil 체크 후 반환한다.
if dbErr != nil {
return nil, dbErr
}
return user, nil
교훈
- 인터페이스 반환 시 구체 타입 변수를 선언하지 말 것
- nil 반환이 필요하면 인터페이스 타입으로 선언하거나 명시적 체크
- Go의 인터페이스는 단순 포인터가 아니라는 점을 항상 기억
이 문제는 Go를 처음 사용하는 개발자들이 자주 겪는 함정이다. 정적 타입 언어지만 인터페이스의 동적 특성 때문에 발생하는 문제였다.