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를 처음 사용하는 개발자들이 자주 겪는 함정이다. 정적 타입 언어지만 인터페이스의 동적 특성 때문에 발생하는 문제였다.

Go 인터페이스와 nil 비교 시 주의사항