전설의 컴퓨터 과학자 Tony Hoare은 ALGOL 언어에 null reference를 소개한 것을 "1조짜리 실수"라고 불렀습니다.
요즘 언어들은 이 실수를 반복하지 않기 위해 두 가지 장치를 고려합니다:
1️⃣ Nullable type (Kotlin, Dart 등)
2️⃣ Option type (Haskell, Rust 등)
둘은 어떤 차이가 있을까요?
null은 "값없음"을 표현할 때 사용됩니다.
유저의 전화번호, 제품의 상세 설명, 서버의 IP 주소 등 값이 없는 상태를 null로 지정해 표현합니다.
하지만 null은 (거의) 모든 변수에 지정될 수 있는 조커 값이라 정적 분석이 힘을 잃습니다. 까먹고 null 체크를 잊으면... NullPointerException 🤦.
Nullable type을 채용한 언어는 룰을 살짝 바꿉니다.
1️⃣ 기본적으로 변수에 null 지정이 불가능합니다.
2️⃣ null 지정이 필요할시 타입 선언에 "이건 null일 수도 있습니다" 알려야 합니다. 보통 "?"를 붙이면 됩니다. (예: int → int?)
변수마다 null의 사용을 명확히 밝히기 때문에 정적 분석에 다시 힘이 실리게 됩니다.
컴파일러는 control flow analysis를 통해 nullable type의 변수가 null인지 아닌지 확인하지 않고 사용하면 컴파일러 에러를 던져 런타임 NullPointerException을 차단할 수 있습니다.
반면 option type을 채택한 언어는 과감히 null을 제거합니다. NullPointerException 개념을 붕괴해버리죠!
null 대신 option type (aka maybe type)이 제공되고, null 같은 "값없음"을 표현하기 위해선 변수 타입을 Option<TYPE>로 선언한 뒤 값은 Some(v) 혹은 None으로 지정합니다.
Option type은 사용 시 손이 좀 더 많이 갑니다.
Nullable type은 null 체크만 하면 원 타입처럼 활용할 수 잇지만 Option<T>은 원 타입 T와 별개인 타입이라 Option<T> 이라는 상자 담긴 값을 꺼내 사용해야 합니다.
하지만 pattern matching을 잘 활용하면 오히려 가독성이 좋아지곤 합니다.
Option type의 None은 null처럼 조커 값이 아닙니다.
Option<int>의 None ("int 값없음") 과 Option<string>의 None ("string 값없음")은 엄연히 다른 None입니다. 좀 더 정교한 "값없음"이 가능해지죠.
예로 key/value cache에서 cache.get(A) 호출 값이 null 이라면 두 가지 해석이 가능합니다:
1️⃣ A 키는 캐시에 없다
2️⃣ A 키의 캐시 된 값은 null이다
반면 null 대신 option type을 사용하면 두 가지 상황을 정확히 표현할 수 있죠:
cache.get(K) -> Option<Option<T>>
1️⃣ None = 캐시에 없다
2️⃣ Some(None) = 캐시 된 값은 null이다
어떤 방식이 더 우월하다 쉽게 말할 순 없지만, nullable type은 이미 null을 허용한 시스템에서 안전성을 더할 때 사용되는 것 같고 (Java → Kotlin, Dart → Null-safe Dart) option type은 강력한 타입이 장착된 언어 (Haskell, Rust)에서 사용 되는 것 같습니다.