Dart 튜토리얼 2편: 변수와 타입 안전성
요약
핵심 요지
문서가 설명하는 범위
- 변수 선언과
타입 추론이 작동하는 방식 nullable과non-nullable의 차이와 실용적 활용late,final,const의 실무 적용 기준
읽는 시간: 16분 | 난이도: 초급
참고 자료
- Variables - 변수 선언, 초기화, 키워드
- Type system - 타입 안전성과 추론
- Sound null safety - null 안전성 원칙
문제 상황
대부분의 프로그래밍 언어에서 초기화하지 않은 변수는 예측 불가능한 값을 가집니다.
JavaScript와 Python 같은 동적 언어는 변수에 어떤 타입의 값이든 넣을 수 있어 편리하지만, 타입 불일치 오류가 runtime8에 발생합니다.
기존 방식의 한계
// JavaScript 예시let count;console.log(count.toString()); // undefined.toString() → 오류
let items = [];items.add(1); // TypeError: items.add is not a functionitems.push("hello"); // 타입 체크 없음문제는 다음과 같습니다.
- 초기화하지 않은 변수가 어떤 값을 가지는지 불명확하다.
- 타입 불일치가 실행 시점에 드러나서 디버깅이 어렵다.
null체크를 빼먹으면 예상치 못한 오류가 발생한다.
해결 방법
Dart는 타입 추론과 null 안전성을 결합해서 변수 관련 오류를 컴파일 단계에서 잡습니다.
단계 1: 타입 추론으로 편리함과 안전성 확보
Why
NOTE타입을 매번 적기 시작하면 코드가 금방 길어집니다.
그래서 “초기값을 보고 타입을 잡는 방식”으로 반복 입력을 줄입니다.String name = 'Bob';int year = 1977;double diameter = 3.7;
What
NOTE
타입 추론은 변수의 초기값을 보고 컴파일러가 타입을 결정하는 기능입니다.
How
TIPDart는 변수 선언 시 타입을 명시하지 않아도 초기값을 보고 타입을 추론합니다.
var키워드를 사용하면 코드가 간결해지면서도 타입 검사는 그대로 작동합니다.var name = 'Bob'; // String으로 추론var year = 1977; // int로 추론var diameter = 3.7; // double로 추론var satellites = ['Moon']; // List<String>으로 추론타입을 명시하고 싶으면 직접 선언할 수도 있습니다.
String name = 'Bob';int year = 1977;
Watch out
WARNING
var로 선언한 변수는 한 번 타입이 추론되면, 다른 타입 값을 다시 넣을 수 없습니다.var year = 1977;// year = '1977'; // 컴파일 오류: year는 int로 추론되었다.
결론: 타입을 적지 않아도 컴파일러가 타입 불일치를 잡아냅니다.
단계 2: nullable과 non-nullable 구분
Why
NOTE
null은 “값이 없음”을 표현할 때 편리하지만, 체크를 빼먹기 쉬워서 오류의 원인이 됩니다.
그래서 “어디가 null이 될 수 있는지”를 타입으로 드러내는 규칙이 필요합니다.String? name = null;// print(name.length); // 컴파일 오류: name이 null일 수 있다.
What
NOTEDart는 기본적으로
non-nullable을 사용하고, 필요할 때만nullable을 명시합니다.
How
TIPDart는 기본적으로 모든 변수가
non-nullable입니다.
null을 허용하려면 타입 뒤에?를 붙여야 합니다.int count = 0; // null 불가int? maybeCount = null; // null 허용String title = 'Dart';String? subtitle; // 초기화하지 않으면 null
nullable변수 사용 시null체크 필수String? name = getName();// 컴파일 오류: name이 null일 수 있음// print(name.length);// 올바른 사용법if (name != null) {print(name.length);}// 또는 null-aware 연산자 사용print(name?.length); // name이 null이면 null 반환
Watch out
WARNING
nullable은 “편의”가 아니라 “정말null이 필요한 경우”에만 선택하는 게 안전합니다.
결론: nullable 변수에 접근할 때 null 체크를 강제해서 runtime 오류를 예방합니다.
단계 3: late로 초기화 시점 미루기
Why
NOTE값은 지금 정할 수 없지만, “사용 전에는 반드시 값이 들어간다”는 상황이 있습니다.
이때nullable로 돌려서 흐림 처리하기보다, 초기화 시점을 분리하는 편이 더 명확할 수 있습니다.// 값을 나중에 넣어도 되지만, 사용할 때는 null이 아니어야 한다.late String description;
What
NOTE
late는 초기화를 나중으로 미루되, 타입은non-nullable로 유지하게 해줍니다.
How
TIP변수를 선언할 때 값이 없지만 사용 전에 반드시 초기화된다면
late키워드를 사용합니다.late String description;void main() {description = 'Feijoada!';print(description); // OK}
lazy initialization9비용이 큰 초기화를 실제 사용 시점까지 미룰 수 있습니다.
late String temperature = readThermometer(); // 사용 전까지 호출 안 됨
Watch out
WARNING
late변수를 초기화하지 않고 사용하면runtime오류가 발생합니다.late String description;void main() {// description = '값';// print(description); // runtime 오류: 초기화되지 않은 late 변수 접근}
결론: 초기화 시점을 제어하면서도 non-nullable 타입을 유지합니다.
단계 4: final과 const로 불변 데이터 선언
Why
NOTE값이 계속 바뀌는 변수는 어디서 값이 바뀌는지 추적하기 어렵습니다.
그래서 “바뀌면 안 되는 값”은 선언 단계에서 잠그는 편이 안전합니다.final name = 'Bob';// name = 'Alice'; // 컴파일 오류: final은 재할당 불가
What
NOTE
final은 한 번만 할당되는 값이고,const는compile-time10에 확정되는 상수입니다.
How
TIP값을 한 번만 할당하고 변경하지 않을 변수는
final또는const로 선언합니다.
final:runtime상수final name = 'Bob'; // 타입 추론final String nickname = 'Bobby';// name = 'Alice'; // 컴파일 오류실행 중에 결정되는 값을
final로 선언할 수 있습니다.final timestamp = DateTime.now(); // runtime에 결정
const:compile-time상수const bar = 1000000; // 숫자 리터럴const double atm = 1.01325 * bar; // 컴파일 시 계산됨
const는 컴파일 시점에 값이 확정되어야 합니다.var foo = const []; // foo는 변수, 값은 constfinal bar = const []; // bar도 변수, 값은 constconst baz = []; // baz 자체가 const (= const [])foo = [1, 2, 3]; // OK: foo는 var이므로 재할당 가능// bar = [1, 2, 3]; // 오류: final은 재할당 불가// baz = [1, 2, 3]; // 오류: const는 재할당 불가설계 의도는 다음과 같습니다.
final: 한 번만 할당되지만runtime에 값이 결정되는 경우const: 컴파일 시점에 값이 확정되는 상수
Watch out
WARNING
final은 “재할당”만 막습니다.
const는 “값 자체가 상수”라서,const로 만든 컬렉션은 수정할 수 없습니다.final list1 = [1, 2, 3];list1.add(4); // OK: final은 재할당만 막는다.const list2 = [1, 2, 3];// list2.add(4); // 오류: const 컬렉션은 수정할 수 없다.
결론: 불변 데이터를 명시적으로 표현해서 의도치 않은 수정을 방지합니다.
한계
Dart 2.x에서는 null 안전 코드와 null 비안전 코드가 섞일 수 있었습니다.
이 경우 unsound null safety11 상태가 되어 보장이 약해집니다.
null비안전 라이브러리와 함께 사용하면non-nullable변수가runtime에null을 가질 가능성이 생깁니다.- Dart 3부터는
null안전이 필수이므로 이 문제가 해결되었습니다.
Footnotes
-
null(널): 값이 없음을 나타내는 특수한 값이다. ↩
-
type inference(타입 추론): 변수의 초기값을 보고 컴파일러가 타입을 자동으로 결정하는 기능이다. ↩
-
non-nullable(널 비허용): null 값을 가질 수 없는 타입이다. ↩
-
late(지연 초기화): 선언 시점이 아닌 나중에 초기화할 수 있게 하는 키워드다. ↩
-
final(파이널): 한 번만 할당 가능한 변수를 선언하는 키워드다. ↩
-
const(상수): 컴파일 시점에 값이 확정되는 상수를 선언하는 키워드다. ↩
-
nullable(널 허용): null 값을 가질 수 있는 타입으로
?를 붙여 표시한다. ↩ -
runtime(런타임): 코드가 실제로 실행되는 시점과 환경을 뜻한다. ↩
-
lazy initialization(지연 초기화): 실제 사용 시점까지 초기화를 미루는 방식이다. ↩
-
compile-time(컴파일 타임): 코드가 컴파일되는 시점을 뜻한다. ↩
-
unsound null safety(불완전한 널 안전): null 안전 코드와 null 비안전 코드가 섞여 보장이 약해지는 상태다. ↩
공유
이 글이 도움이 되었다면 다른 사람과 공유해주세요!