본문 바로가기

Language Syntax/Dart

[Dart] 널 세이프티 (Null Safety)

728x90

1. 개요

Dart 에서는 2.0 버전부터 null 값에 대하여 안전한 대응이 가능하도록 하는 Null Safety 를 도입하였다. 이번 포스팅에서는 이것이 적용된 이유와 이와 관련된 여러 가지 문법에 대해 알아보고자 한다.

2. 적용 이유

Null Safety는 예상치 못한 null 값에 대한 오류에 대한 검출을 런타임 오류가 아닌 컴파일 오류 차원에서 가능하도록 할 수 있다.

다음의 예시에서 확인할 수 있다.

bool isEmpty(String str) => str.length == 0;
void main() { isEmpty(null); }

[출처: Dart 공식 문서]

 

위 코드는 NoSuchMethodError 를 발생시킨다. Null 클래스에는 length 라는 getter 가 존재하지 않기 때문이다.

 

하지만 isEmpty(null) 의 코드를 보고

String 형식의 매개변수에 null 값을 넣었으니 당연한 결과이며, null 이 아닌 String 형식의 값을 전달하면 문제없지 않을까?

라고 반문할 수 있을 것이다.

 

그러나 null 자체가 값이 없음 을 의미하며, 프로그램 개발에 있어 null 값의 발생 여부는 대부분 예상치 못하게 일어나는 경우가 많다. 다음의 예시를 보자.

void main() async {
  String name;
  /* name 을 불러오는 로직 */
  name = await loadName();
  isEmpty(name);
}

loadName() 의 함수가 어떻게 구현되었는지는 모르지만, 어떤 DB로 부터 이름(name)값을 불러온다고 가정하자.

DB에 이름 데이터가 비어있든지의 이유로 불러오기에 실패했을 경우 name 변수에는 어떠한 값도 저장되지 못할 수 있다. 이 상태가 바로 null 이다.

Null Safety 는 이와 같은 오류를 컴파일 단계에서 검출할 수 있도록 고안되었다.

String name;

위와 같이 변수를 선언할 경우, name 변수는 null 값을 가질 수 없음이 보장된다. 하지만 사실 위와 같은 선언 방식 자체가 Null Safety 에서 허용되지 않는다. name 변수는 null 값을 가질 수 없음과 동시에 선언 후 초기화되지 않았으므로 값이 없기 때문에 모순되기 때문이다. 따라서 Non-nullable 변수는 선언과 동시에 초기화 되어야 한다.

3. 문법

3.1. Non-nullable / Nullable

3.1.1. Non-nullable

기존의 변수 선언 방식은 모두 Non-nullable 변수를 선언하도록 변경되었다.

int a = 3;
int b;        // compile error
int c = null; // compile error

위와 같이 Non-nullable 변수를 선언하기 위해서는 반드시 초기화되어야 하고, 의도적으로 null을 포함할 수 없다.

3.1.2. Nullable

기존과 같이 변수가 null 값을 포함할 수 있도록 허용할 수 있는데, 형식 뒤 ? 를 추가하여 Nullable 임을 명시할 수 있다.

int? a = 3;
int? b;
int? c = null;

위의 경우 오류가 발생하지 않는다.

3.2. late 연산자

int a; // compile error

기본적으로 Non-nullable 변수는 선언과 동시에 초기화되어야 한다고 하였다. 하지만 의도적으로 초기화를 지연시킬 수 있는데, late 연산자를 사용하면 가능하다.

late int a;
a = 3;

late 연산자를 사용하여 초기화를 지연시킨 변수는 초기화 전까지 코드 내 어떠한 상호작용도 불가능하다. 만약 초기화 전 변수를 사용할 경우, LateInitializationError 를 발생시킬 것이다.

3.3. Nullable 변수 처리 문법

3.3.1. ! (Null 단언 연산자)

Null 단언 연산자 (Null-assertion Operator) 는 Nullable 형식의 변수가 null 값을 가지지 않음을 보장할 수 있을 때 사용한다.

String? str;
print(str!);        // runtime error (TypeError)
print(str!.length); // runtime error (TypeError)

str = 'Hello world!';
print(str!);        // Hello world!
print(str!.length); // 12

Null 단언 연산자 를 사용하였지만 변수에 null 값이 검출되었을 경우, 런타임 오류 (TypeError) 를 발생시킨다.

3.3.2. ? (Null 인지 연산자)

Null 인지 연산자 (Null-aware Operator) 는 Nullable 형식의 변수가 null 값을 가질 때, 적절한 대처가 가능하도록 도와주는 연산자이다.

  • ?. 연산자

<nullable>?.<attribute or method> 의 형태로 사용되며 Nullable 변수(객체)가 null 일 경우 null 을, null 이 아닐 경우 객체 내 속성 및 메소드에 접근하여 값을 반환한다.

String? str;
print(str?.length); // null

str = 'Hello world!';
print(str?.length); // 12
  • ?? 연산자

<nullable> ?? <value> 의 형태로 사용되며, 좌우의 피연산자(operand) 에 대하여 null 이 아닌 값을 선택한다.

int? a, b = 3;
print(a ?? b); // 3
print(b ?? a); // 3
print(a ?? 5); // 5

?? 연산자는 다음 코드의 축약형이라고 할 수 있다.

a ?? b;
a == null ? b : a;
a != null ? a : b;
  • ??= 연산자

<nullable> ??= <value> 의 형태로 사용되며, Nullable 형식의 변수가 null 일 때에 한해 값을 value 로 최신화한다.

int? a, b;
a ??= b; print(a); // null
a ??= 3; print(a); // 3
a ??= 5; print(a); // 3

??= 연산자는 다음 코드의 축약형이라고 할 수 있다.

a ??= b;
if (a == null) a = b;

참고 자료

728x90

'Language Syntax > Dart' 카테고리의 다른 글

[Dart] factory 예약어에 대한 고찰  (0) 2024.01.09
[Dart] 익스텐션 (Extension)  (0) 2023.10.31
[Dart] get / set  (0) 2023.10.31