~songbly/blog
Published on

Flutter 에서 null safety 이해하기 (2) - Type Promotion (1)

Authors

지난 포스팅에서 Dart에서 null safety를 대응하는 몇가지 방법을 알아봤었다.
몇가지 리뷰를 해보자면,
특정 필드값이 함수 외부로 이동했을 때, Dart는 해당 필드가 null이 아니라는 것을 이해하는 능력을 잃어버리는 것을 알 수 있었다.
Dart 는 런타임이 아닌, 컴파일 타임에서 코드를 분석하여, null을 허용하는 필드를 구별 할 수 있었고, 특정 코드에 도달 했을 때, 그 값이 유효한지 아닌지도 알 수 있었다.

오늘은 유형승격이 뭔지, 또 어떻게 null safety 대응을 할지 알아보자.

유형 승격 (Type Promotion)

void main() {
  Object a = 'This is a string';
  print(a.length);
}

위 코드에서 객체 a는 문자열 값에 할당된다.
Object 는 기본 클래스 이지만, 문자열 값을 할당 할 수 있다. 그러면 문자열의 길이는 구할 수 있을까?

Dart에서는 이것이 허용되지 않는다. 할당된 값은 문자열이지만, 객체 타입으로 선언 됐기 때문에, 문자열의 속성은 사용할 수 없어서 아래와 같은 에러가 발생한다.

Error: The getter 'length' isn't defined for the class 'Object'.

그런데 신기하게도, 코드 한줄만 추가하면 위의 에러를 없앨 수 있다. 아래와 같이, Dart가 a 값이 문자열이라는 것을 보장 받으면, 해당 코드는 정상적으로 실행된다.

void main() {
  Object a = 'This is a string';
  if (a is String) {
    print(a.length);
  }
}

이것이 가능한 이유는, Dart 가 코드를 읽어 내려 오면서 print(a.length); 에 도달할 때, a가 문자열이어야 한다는 것을 알고 있다는 것이다.
Dart는 저 조건을 추가 함으로, 동일한 함수 내부에 있는 변수를 이미 확인 했기 때문이다.
이를 통해, 동일한 스코프 내에서 특정 유형인지 확인하는 것을 알 수 있다.

이것이 기본 클래스(Object)에서 파생 클래스(String)으로 Type Promotion이 발생했다고 한다.
그럼 이렇게 Type Promotion을 하는 것이 null safety 에 어떻게 작동되는지 자세히 살펴 보자.

아래의 코드도 dartpad에서 실행시키면, 위 코드와 같이 동일하게 에러가 발생한다.

void main() {
  String? text;

  print(text);
  print(text.length);
}

당연하게도, text는 nullable 타입이고, 아무것도 할당 받지 않았다. 이 상태에서 텍스트 길이 속성에 접근하게 되면, Dart는 print 되는 시점에 text가 null 인 것을 알고 있기 때문에 에러를 내뿜는다.

이런 경우에 우리는 Dart에게 text는 확실히 null이 아니니까 걱정하지 말아! 라는 것을 알려줘야 한다.

이것을 Dart에게 알려주는 방법은 몇가지가 있는데, 우선 조건을 걸어주는 방법을 해보자.

아래와 같이 text의 길이 속성에 접근할 때, text 뒤에 ?를 붙여주면, text가 null이 아닐 때, 길이 속성에 접근해서 print 해줘!가 된다.

void main() {
  String? text;

  print(text);
  print(text?.length);
}

동시에 text가 null 일 경우에는, 해당 속성(문자열 길이 속성)이 없기 때문에, 해당 속성에 접근을 시도하지말고 null을 반환해!를 내포하고 있다. 이 방법은 메서드와 속성애 대한 호출을 보호하는 방법이다.

Definite assignment (확정 할당)

또 다른 방법으로는 아래와 같이 조건을 추가 해주면 에러가 사라진다.

void main() {
  String? text;

  if (DateTime.now().hour < 12) {
    text = "It's morning! Let's make aloo paratha!";
  } else {
    text = "It's afternoon! Let's make biryani!";
  }

  print(text);
  print(text.length);
}

왜 에러가 사라질까?
그 이유는, Dart는 코드를 읽어 내려오면서, 분기를 태울 것을 알 수 있고, 분기가 2개인 것을 알고 있으며, 두가지 분기에서 null이 아닌 텍스트 값이 할당이 되고 있는 것을 알 수 있기 때문이다. 그리고 print(text.length);에 도달할 때에는, 텍스트에 어떤 값이 할당되어 있어서 문자열 길이 속성에 접근 할 수 있다는 것을 알 수 있다. 따라서 Dart는 null을 허용 하지 않는 필드로 혹은 null을 허용 하지 않는 변수로 처리 되도록 자동으로 type promotion 을 진행시킨다.

이것이 null 허용 필드의 type promotion 이고, null 허용이 아닌 필드로 처리되는 방법이다.

Null checking (Null 체크하기)

아래의 코드도 dartpad를 통해 실행시켜 보면, 에러가 발생한다.

int getLength(String? str) {
  return str.length;
}
void main() {
  print(getLength('This is a string'));
}

위에서 살펴 봤듯이, str.length 에서 에러가 나는 이유는 str이 null일 수 있는 상황에서 문자열 길이 속성에 접근 할 수 없기 때문이다.

아래와 같이 Dart가 return str.length; 해당 코드에 도달 하였을 때, string이 null이었다면 이미 반환되어 있을 것을 알려주면 된다.

int getLength(String? str) {
  if(str == null) {
    return 0;
  }
  return str.length;
}
void main() {
  print(getLength('This is a string'));
}

Exception

또, 예외를 통해 type promotion 시킬 수 있다. 아래의 코드를 보면, return str.length; 코드에 도달하는 시점에서, 이미 str이 null이면 튕겨져 나왔을 것을 Dart는 알고 있기 때문에, 에러를 발생시키지 않는다.

int getLength(String? str) {
  if(str == null) {
    throw 'Hey, that string was null!';
  }
  return str.length;
}
void main() {
  print(getLength('This is a string'));
}

여기까지 Dart 가 Type Promotion 을 통해 어떻게 Null Safety 대응하는지를 알아보았다. 다음 포스팅에서, 좀 더 상세한 예제들을 업로드 해보도록 하겠다.