용어집(Glossary)
용어집(Glossary)
dart.dev 전반에서 쓰는 용어를 한곳에 정리한 참고 자료예요. 초보자부터 경험자까지, 문서를 읽다가 낯선 개념을 만나면 이 용어집을 찾아보면 도움이 돼요.
출처: Glossary
본문
Dart 문서 전반에서 사용하는 용어들의 정의를 정리했어요.
AOT
빌드 시점에 최적화된 네이티브 바이너리를 만들어내는 컴파일 방식이에요.
Ahead-of-Time(AOT) 컴파일은 빌드 시점에 Dart 코드를 네이티브 머신 코드로 변환해요. 그 결과 더 작고 빠른 실행 파일이 만들어지는데, 즉시 시작되고 일관된 성능으로 실행돼요. AOT 빌드는 사용하지 않는 코드를 제거하기 위해 트리 셰이킹(tree-shaking)이 적용되며, 프로덕션 앱 배포에 사용돼요.
관련 문서와 자료
Application package
실행 가능한 애플리케이션을 담고 있는 Dart 패키지예요.
application package(애플리케이션 패키지)는 메인 엔트리포인트(main entrypoint)(main entrypoint)를 가진 프로그램이나 앱을 담고 있는 Dart 패키지예요. 명령줄, 브라우저, 또는 Flutter가 제공하는 것 같은 다른 임베더(embedder)에서 직접 실행하도록 만들어졌어요.
애플리케이션 패키지는 다른 패키지(package)에 대한 의존성(dependencies)(dependency)을 가질 수 있지만, 그 자체가 의존성의 대상이 되는 일은 없어요. 일반 패키지와 달리 공유를 목적으로 하지 않아요.
애플리케이션 패키지는 락파일(lockfiles)(lockfile)을 소스 제어에 포함시켜야 해요. 그래야 애플리케이션에서 작업하는 모든 사람과 애플리케이션이 배포되는 모든 곳에서 일관된 의존성 집합을 유지할 수 있거든요.
관련 문서와 자료
Assist
코드를 일반적으로 개선하는 것을 목표로 하는 자동화된 로컬 코드 편집이에요.
assist(어시스트)는 코드를 흔히 개선하는 것을 목표로 하는 자동화된 로컬 코드 편집이에요. 어시스트의 예로는 switch 문을 switch 표현식으로 변환하기, if 문에서 then과 else 블록 순서 바꾸기, 위젯 구조에 위젯 삽입하기 등이 있어요.
관련 문서와 자료
Asynchronous
Dart에서 I/O나 네트워크 호출 같은 작업이 이벤트 루프를 막지 않고 나중에 완료되는 프로그래밍 패러다임이에요.
Dart는 비동기 프로그래밍을 위해 단일 스레드 이벤트 루프 모델을 사용해서, 파일 I/O, HTTP 요청, 타이머 같은 시간이 걸리는 작업을 비차단 방식으로 실행할 수 있게 해줘요. 완료될 때까지 블로킹되는 동기 코드와 달리, 비동기 작업은 마이크로태스크/이벤트를 통해 큐에 쌓여서 UI가 반응성을 유지해요.
핵심 구성 요소는 다음과 같아요.
- Futures:
http.get()같은 API가 만들어내는, 나중에 값을 나타내는 객체예요. - async/await: 유지보수 가능하고 읽기 쉬운 비동기 코드를 작성하기 위한 Dart 키워드예요.
- Streams: 웹 소켓 데이터 같은 비동기 이벤트의 시퀀스를 처리해요.
- Isolates: 메시지 전달을 통한 독립적인 실행 컨텍스트로 진정한 병렬 처리를 가능하게 해요.
관련 문서와 자료
- Asynchronous programming: futures, async, await
- Asynchronous programming overview
- dart:async library
- Future class reference
Bottom type
값이 없으면서 다른 모든 타입의 서브타입인 타입이에요.
Dart의 bottom type(바텀 타입)은 값이 없는 타입으로, 다른 모든 타입의 서브타입(subtype)으로 간주돼요. Subtype은 상위 타입(supertype)의 값이 기대되는 곳 어디든 사용할 수 있는 타입을 말해요. 더 알아보기(Learn more)
Dart에서 bottom type은 Never 타입으로 표현돼요.
이는 Never 타입의 값은 어디에나 사용될 수 있다는 뜻이에요. 그런 값은 사실 존재할 수 없으니까요. 주로 함수의 반환 타입으로 쓰여서, 예외를 던지거나 무한 루프를 도는 것처럼 절대 반환하지 않는 함수를 나타내요.
예를 들어, 다음 fail 함수는 항상 예외를 던지기 때문에 반환 타입을 Never로 선언해 결코 반환하지 않음을 나타내요.
Never fail(String message) {
throw Exception(message);
}
void main() {
String result = fail('Oops'); // OK: Never is a subtype of String.
}
fail은 절대 반환하지 않으므로, 그 결과를 String에 할당하는 것이 허용돼요.
관련 문서와 자료
Callback
나중에 호출되도록 다른 함수에 인자로 전달되는 함수예요.
callback(콜백)은 다른 함수에 인자로 전달해서 나중에 호출(또는 "call back")될 수 있게 하는 함수예요.
콜백은 주로 다음에 사용돼요.
- 이벤트 처리
- API의 동작 사용자 정의
- future의 결과에 응답하는 것 같은 비동기 작업
예를 들어, 다음 코드 조각에서 printResult 함수는 doOperation에 콜백으로 전달되고, 연산이 끝난 뒤 호출돼요.
void doOperation(int a, int b, void Function(int) callback) {
final result = a + b;
callback(result);
}
void printResult(int value) {
print('The result is $value.');
}
void main() {
doOperation(3, 4, printResult); // Prints: The result is 7.
}
관련 문서와 자료
Closurization
메서드나 함수를 클로저로 바꾸는 과정이에요.
Closurization(클로저화)은 Dart가 메서드나 함수를 저장·전달·나중에 호출할 수 있는 함수 객체(또는 tear-off)로 바꾸는 과정이에요.
tear-off는 기존 함수나 메서드에 대한 참조로, 해당 엔티티의 함수 객체로 평가되지만 호출하지는 않아요. 함수를 tear-off 하면 런타임(또는 컴파일러)이 그 함수를 클로저로 감싸서 Dart의 다른 함수 객체처럼 동작하게 해요.
예시:
class Greeter {
void greet(String name) {
print('Hello, $name!');
}
}
void main() {
final greeter = Greeter();
// Tear-off of the instance method `greet`.
final sayHello = greeter.greet;
sayHello('Dart'); // Prints: Hello, Dart!
}
이 예시에서 greeter.greet는 인스턴스 greeter를 캡처한 함수 객체로 클로저화돼요. sayHello를 호출하면 마치 greeter.greet(...)를 직접 호출한 것처럼 실행돼요.
클로저화는 tear-off가 함수가 기대되는 곳 어디에서든 사용될 수 있도록 보장해요.
관련 문서와 자료
Code asset
빌드 훅을 사용해 Dart 앱에 번들되는 컴파일된 네이티브 코드로, dart:ffi를 통해 사용할 수 있어요.
code asset(코드 에셋)은 Dart로 작성되지 않았지만 Dart 애플리케이션이 패키징하고 사용할 수 있는 컴파일된 코드예요. C, C++, Rust 같은 언어로 빌드한 공유 라이브러리(.so, .dll, .dylib)가 예시에요.
코드 에셋은 빌드 훅(build hook)(build hook)을 사용해 Dart 패키지에 번들할 수 있어요. Dart 코드는 FFI(FFI)를 사용해 이 에셋을 호출할 수 있어요.
역사적으로 코드 에셋은 네이티브 에셋(native assets) 또는 **네이티브 코드 에셋(native code assets)**이라고도 불렸어요.
코드 에셋은 다음과 같은 경우에 유용해요.
- 기존 네이티브 라이브러리와의 통합
- Dart에서 직접 노출되지 않는 시스템 기능에 접근
- 성능이 중요한 기능을 플랫폼 간에 공유
예를 들어, Dart 애플리케이션은 특정 함수를 노출하는 네이티브 .so 라이브러리를 포함하고, 그 함수를 Dart에서 호출할 수 있어요.
관련 문서와 자료
Combinator
가져오거나 내보내는 대상을 제한하거나 수정하는 키워드 절이에요.
Dart에서 combinator(컴비네이터)는 import 또는 export 지시문 뒤에 오는 절로, 스코프로 가져오는 이름 집합을 제한하거나 수정해요.
Dart는 두 종류의 combinator를 지원해요.
show— 특정 이름을 명시적으로 포함시켜요.hide— 특정 이름을 제외해요.
combinator는 여러 라이브러리가 같은 이름의 심볼을 정의할 때 네임스페이스 오염과 충돌을 관리하는 데 도움을 줘요.
show를 사용한 예:
import 'dart:math' show pi, sqrt;
void main() {
print(pi); // Accessible
print(sqrt(9)); // Accessible
// print(Random()); // Error: Random is not imported.
}
hide를 사용한 예:
import 'dart:math' hide pi;
void main() {
// print(pi); // Error: pi is hidden.
print(Random()); // Accessible
}
관련 문서와 자료
Constant context
const 키워드가 암시되고, 해당 영역 안의 모든 것이 상수여야 하는 코드 영역이에요.
constant context(상수 컨텍스트)는 const 키워드를 포함할 필요가 없는 코드 영역이에요. 그 영역 안의 모든 것이 상수여야 한다는 사실 때문에 const가 암시되기 때문이에요. 상수 컨텍스트가 되는 위치는 다음과 같아요.
const키워드가 붙은 리스트·맵·셋 리터럴 안의 모든 것. 예:
var l = const [/*constant context*/];
- 상수 생성자 호출 안의 인자들. 예:
var p = const Point(/*constant context*/);
const키워드가 붙은 변수의 초기화식. 예:
const v = /*constant context*/;
-
애너테이션(annotation).
-
case절의 표현식. 예:
void f(int e) {
switch (e) {
case /*constant context*/:
break;
}
}
관련 문서와 자료
Context type
주변 코드가 표현식에서 기대하는 타입이에요.
context type(컨텍스트 타입)은 주변 코드가 표현식에서 기대하는 타입이에요. 예를 들어 변수 타입, 매개변수 타입, 또는 반환 타입이에요.
Dart는 컨텍스트 타입을 사용해 표현식을 해석하고 의미를 추론해요. 다음을 포함해서요.
- 타입 추론(Type inference)("하향 추론"):
List<int> list = [];
컨텍스트 타입 List<int> 덕분에 컴파일러가 리스트 타입을 <int>[]로 추론할 수 있어요.
- 암시적 다운캐스트(Implicit downcast):
String asString(dynamic value) => value;
반환 컨텍스트가 String이므로 Dart는 암시적 다운캐스트를 삽입해요.
- 리터럴 해석(Literal interpretation):
double d = 0;
컨텍스트 타입 double이 0을 0.0처럼 동작하게 만들어요.
- 정적 접근 단축 표기(Static access shorthand)(점 단축 표기):
int x = .parse(input);
컨텍스트 타입이 int이므로 .parse는 int.parse(input)으로 해석돼요.
컨텍스트 타입이 없는 표현식도 있어요. 다음을 포함해서요.
- 문장으로 사용될 때:
set.remove(value);같은 표현식은 값이 아니라 효과만을 위해 사용되므로 기대 타입이 없어요. - 컨텍스트 타입이 표현식에서 추론될 때: 예를 들어
var list = [1];에서 리스트 리터럴은 컨텍스트 타입이 없어요. Dart는 내용에서List<int>를 추론해 그 타입을 변수에 할당해요.
관련 문서와 자료
Dart SDK constraint
패키지가 지원하는 Dart 버전을 말해요.
패키지가 스스로 지원한다고 선언한 Dart SDK 버전의 범위예요. SDK 제약은 일반적인 버전 제약(version constraint)(version constraint) 문법으로 지정하되, pubspec의 특별한 environment 섹션(in the pubspec)(in the pubspec)에 지정해요.
관련 문서와 자료
Definite assignment
변수가 사용되기 전에 확실히 값을 할당받았는지 여부를 판정하는 것을 말해요.
definite assignment 분석은 각 코드 지점의 각 지역 변수에 대해 다음 중 어느 것이 참인지 판정하는 과정이에요.
- 변수가 확실히 값을 할당받았는지(definitely assigned).
- 변수가 확실히 값을 할당받지 않았는지(definitely unassigned).
- 해당 지점에 도달하는 실행 경로에 따라 값을 할당받았을 수도 있고 아닐 수도 있는지.
definite assignment 분석은 코드에서 문제를 찾는 데 도움을 줘요. 예를 들어 값을 할당받지 않았을 수도 있는 변수를 참조하는 곳이나, 한 번만 값을 할당받을 수 있는 변수가 이미 할당됐을 수도 있는데 다시 할당되는 곳을 찾아내요.
예를 들어, 다음 코드에서 변수 s는 print에 인자로 전달될 때 확실히 할당되지 않은 상태(definitely unassigned)예요.
void f() {
String s;
print(s);
}
하지만 다음 코드에서는 변수 s가 확실히 할당돼요.
void f(String name) {
String s = 'Hello $name!';
print(s);
}
definite assignment 분석은 실행 경로가 여러 개일 때도 변수가 확실히 할당됐는지(또는 할당되지 않았는지) 알려줄 수 있어요. 다음 코드에서 print 함수는 if 문의 참 또는 거짓 분기 어느 쪽으로 실행되든 호출돼요. 하지만 어떤 분기를 타든 s가 할당되므로, print에 전달되기 전에 확실히 할당됐어요.
void f(String name, bool casual) {
String s;
if (casual) {
s = 'Hi $name!';
} else {
s = 'Hello $name!';
}
print(s);
}
흐름 분석에서 if 문의 끝은 join(조인)이라고 불러요. 둘 이상의 실행 경로가 다시 합쳐지는 지점이죠. 조인 지점에서 분석은, 합쳐지는 모든 경로에서 확실히 할당됐다면 변수가 확실히 할당된 것으로 보고, 모든 경로에서 확실히 할당되지 않았다면 확실히 할당되지 않은 것으로 봐요.
때로는 변수가 한 경로에서는 할당되고 다른 경로에서는 할당되지 않기도 해요. 이 경우 변수는 값을 할당받았을 수도 있고 아닐 수도 있어요. 다음 예시에서 if 문의 참 분기는 실행될 수도 있고 안 될 수도 있으므로, 변수는 값을 할당받았을 수도 아닐 수도 있어요.
void f(String name, bool casual) {
String s;
if (casual) {
s = 'Hi $name!';
}
print(s);
}
s에 값을 할당하지 않는 거짓 분기가 있어도 마찬가지예요.
루프의 분석은 조금 더 복잡하지만, 같은 기본 원리를 따르는 것뿐이에요. 예를 들어 while 루프의 조건은 항상 실행되지만, 본문은 실행될 수도 있고 안 될 수도 있어요. 그래서 if 문처럼 while 문의 끝에도 조건이 true인 경로와 false인 경로 사이의 join이 있어요.
관련 문서와 자료
Dependency
패키지가 의존하는 다른 Dart 패키지예요.
dependency(의존성)는 패키지가 의존하는 다른 Dart 패키지(package)예요. 여러분의 패키지가 다른 패키지의 코드를 import 하려면, 그 패키지가 먼저 여러분의 의존성이어야 해요. 의존성은 패키지의 pubspec(pubspec) 파일에 Package dependencies(Package dependencies)에 설명된 문법으로 지정돼요.
패키지가 사용하는 의존성을 보려면 pub deps(pub deps)를 사용하세요.
관련 문서와 자료
Dependency graph
시스템에서 구성 요소들이 서로 의존하는 방식을 나타낸 것이에요.
dependency graph(의존성 그래프)는 시스템 내 서로 다른 구성 요소(파일, 모듈, 패키지, 함수, 에셋 등) 사이의 관계를 보여주는 방향 그래프(directed graph)예요. 각 노드는 구성 요소를 나타내고, 각 엣지는 두 구성 요소 사이의 의존성을 나타내요.
Dart와 Flutter에서 의존성 그래프는 주로 다음과 같은 도구들이 사용해요.
- 컴파일러: 빌드 순서를 결정.
- 패키지 관리자: 패키지 버전을 해석.
- 빌드 시스템: 다시 빌드해야 할 것을 감지.
- 트리 셰이킹: 사용하지 않는 코드 제거.
예를 들어, main.dart 파일이 utils.dart를 import 하고, utils.dart가 다시 math.dart를 import 한다면, 의존성 그래프는 컴파일러가 이 파일들을 처리하는 올바른 순서와 프로그램의 어떤 부분이 실제로 사용되는지 이해하는 데 도움을 줘요.
의존성 그래프는 다음에 필수적이에요.
- 효율적인 빌드
- 죽은 코드 제거(트리 셰이킹)
- 의존성 해석
- 증분 컴파일
관련 문서와 자료
Dependency source
pub이 패키지를 가져올 수 있는 장소의 종류예요.
pub이 패키지를 가져올 수 있는 저장소나 위치의 한 유형이에요. 소스(source)는 pub.dev 사이트나 특정 git URL 같은 특정 장소가 아니에요. 각 소스는 패키지에 접근하는 일반적인 절차를 설명해요.
예를 들어, git은 지원되는 의존성 소스 중 하나예요. git 소스는 git URL이 주어졌을 때 패키지를 다운로드하는 방법을 알고 있어요. 여러 지원되는 소스(supported sources)를 사용할 수 있어요.
관련 문서와 자료
Dot shorthand
생성자나 정적 멤버(열거형 값 포함)에 접근할 때 타입 이름을 생략할 수 있게 해주는 단축 문법이에요.
dot shorthand(점 단축 표기)는 생성자나 정적 멤버(열거형 값 포함)에 접근할 때 타입 이름을 생략해서 더 간결한 코드를 쓸 수 있게 하는 언어 기능이에요.
이 기능은 표현식의 기대 타입이 주변 컨텍스트(컨텍스트 타입, context type)(context type)에서 유일하게 추론될 때만 허용돼요.
예를 들어, Status.running이라고 쓰는 대신, 컨텍스트 타입이 이미 Status로 알려져 있다면 .running이라고 쓸 수 있어요.
enum Status { running, stopped }
void updateStatus(Status s) {
print('Status updated to: $s');
}
void main() {
updateStatus(.running); // Resolves to Status.running
}
관련 문서와 자료
Entrypoint
Dart 구현에 의해 직접 호출되는 Dart 라이브러리예요.
일반적인 Dart 맥락에서 entrypoint(엔트리포인트)는 Dart 구현이 직접 호출하는 Dart 라이브러리예요. 예를 들어, 독립형 Dart VM의 명령줄 인자로 Dart 라이브러리를 전달하면, 그 라이브러리가 엔트리포인트가 돼요. 다시 말해, 보통은 main()을 담고 있는 .dart 파일이에요.
pub 맥락에서 entrypoint package(엔트리포인트 패키지) 또는 root package(루트 패키지)는 의존성 그래프의 루트예요. 보통 애플리케이션이죠. 앱을 실행할 때 그 앱이 엔트리포인트 패키지가 돼요. 그 앱이 의존하는 다른 모든 패키지는 그 맥락에서 엔트리포인트가 아니에요.
패키지는 어떤 맥락에서는 엔트리포인트일 수 있고, 다른 맥락에서는 아닐 수 있어요. 여러분의 앱이 패키지 A를 사용한다고 해볼게요. 앱을 실행할 때 A는 엔트리포인트 패키지가 아니에요. 하지만 A로 가서 그 테스트를 실행하면, 그 맥락에서는 여러분의 앱이 관여하지 않으므로 A가 엔트리포인트예요.
관련 문서와 자료
Entrypoint directory
Dart 엔트리포인트를 담을 수 있는 디렉토리예요.
entrypoint directory(엔트리포인트 디렉토리)는 Dart 엔트리포인트(Dart entrypoints)를 담을 수 있는 여러분의 Dart 패키지(package) 안의 디렉토리예요.
pub은 이런 디렉토리 목록을 갖고 있어요: benchmark, bin, example, test, tool, web(그리고 Flutter 앱(Flutter apps)의 경우 lib). 이들의 하위 디렉토리(bin 제외)도 엔트리포인트를 담을 수 있어요.
관련 문서와 자료
Extension type
기반 타입에 대해 제로 비용 추상화를 제공하는 컴파일 타임 래퍼예요.
extension type(익스텐션 타입)은 기반 표현 타입(representation type) 주위에 정적이고 컴파일 타임에만 존재하는 래퍼를 도입해요.
익스텐션 타입은 컴파일 타임에 지워지기 때문에 런타임 메모리나 CPU 비용이 제로(0)이면서도, 구별되고 타입 안전한 API를 제공해요.
관련 문서와 자료
Function
최상위 함수, 지역 함수, 정적 메서드, 인스턴스 메서드를 총칭하는 용어예요.
관련 문서와 자료
Getter
객체의 프로퍼티에 읽기 접근을 제공하는 특수 메서드예요.
getter와 setter는 객체의 프로퍼티에 읽기·쓰기 접근을 제공하는 특수 메서드예요. 각 인스턴스 변수에는 암시적 getter가 있고, 적절하다면 암시적 setter도 있다는 점을 기억하세요. get과 set 키워드를 사용해 getter와 setter를 구현함으로써 추가 프로퍼티를 만들 수 있어요.
관련 문서와 자료
Immediate dependency
Dart 패키지가 직접 사용하는 의존성이에요.
immediate dependency(직접 의존성)는 패키지가 직접 사용하고 스스로 선언하는 의존성(dependency)이에요. pubspec.yaml(pubspec.yaml) 파일에 나열한 의존성이 패키지의 직접 의존성(direct dependencies)이에요. 그 외의 모든 의존성은 전이 의존성(transitive dependencies)(transitive dependencies)이에요.
관련 문서와 자료
Immutable
생성된 후에 중첩된 모든 값을 포함해 상태를 바꿀 수 없는 객체예요.
immutable(불변) 객체는 생성된 후에 상태를 수정할 수 없는 객체예요. 객체가 불변이라면, 모든 필드는 final이어야 하고(재할당 불가), 그 필드의 값들도 그 자체로 불변이어야 해요(변경 불가). 이는 일관성을 보장하고, 동시성 또는 반응형 코드에서 더 안전하게 사용할 수 있게 해줘요.
Dart에서 클래스가 불변이라면 다음을 지켜야 해요.
- 모든 필드를 final로 선언해서 재할당할 수 없게 해요.
- 필드 값 자체가 불변임을 보장해요.
- 선택적으로, meta 패키지의
@immutable애너테이션을 사용해요. 이렇게 하면 분석기가 어떤 필드가 final이 아니거나 변경 가능한 타입을 참조하는지 경고해 줘요.
추가로, 모든 Dart const 값은 불변이에요. 예를 들어 const [1, 2, 3]은 불변 리스트를 만들어요. 클래스에 (non-factory) const 생성자가 있다면, 모든 필드는 final이어야 해요.
예시:
import 'package:meta/meta.dart';
@immutable
class User {
final String name;
final int age;
const User(this.name, this.age);
}
위 예시에서 User 인스턴스는 생성된 후 수정할 수 없어요. 데이터를 바꾸려면 새 인스턴스를 만들어야 해요.
관련 문서와 자료
Instantiate
클래스의 구체적인 인스턴스(객체)를 만드는 것.
클래스를 instantiate(인스턴스화)한다는 것은 그 클래스 타입의 객체를 메모리에 만든다는 뜻이에요. Dart에서는 final myItem = Item()처럼 생성자를 호출해서 만들어요. 추상 클래스는 직접 인스턴스화할 수 없어요.
관련 문서와 자료
Interop
Dart 코드가 다른 언어로 작성된 코드와 상호작용할 수 있는 능력이에요.
Interop(interoperability의 줄임말)은 Dart가 다른 프로그래밍 언어로 작성된 코드와 통신하고 함께 작동할 수 있는 능력을 말해요.
Dart에서 interop은 주로 다음에 사용돼요.
- C, C++, Rust 같은 언어로 작성된 네이티브 코드를 호출.
- 플랫폼별 API와 상호작용.
- Dart로 작성되지 않은 기존 라이브러리 재사용.
Dart는 플랫폼과 사용 사례에 따라 다양한 interop 메커니즘을 제공해요. 예를 들어, Dart 애플리케이션은 Foreign Function Interface(FFI)를 사용해 네이티브 라이브러리를 호출하거나, Flutter나 웹에서 실행할 때 플랫폼별 interop 계층을 사용할 수 있어요.
관련 문서와 자료
Irrefutable pattern
항상 매칭되는 패턴이에요.
irrefutable patterns(반박 불가 패턴)은 항상 매칭되는 패턴이에요. irrefutable 패턴은 irrefutable contexts(반박 불가 컨텍스트), 즉 선언(declaration)(declaration) 및 할당(assignment)(assignment) 패턴 컨텍스트에만 나타날 수 있어요.
관련 문서와 자료
Just-in-Time compilation (JIT)
빠른 개발 주기를 위해 런타임에 코드를 컴파일하는 컴파일 모드예요.
Just-in-Time(JIT) 컴파일은 코드가 실행되는 동안 Dart 코드를 네이티브 머신 코드로 컴파일해요. 이는 증분 컴파일을 가능하게 해서 핫 리로드(hot reload) 와 풍부한 디버깅 같은 기능을 제공해요. JIT는 보통 개발 중에 사용되고, 프로덕션 빌드는 AOT를 사용해요.
관련 문서와 자료
Late
변수의 지연 초기화를 가능하게 하는 키워드로, 보통 non-nullable 변수와 함께 사용돼요.
Dart의 late 키워드는 변수가 선언된 뒤, 하지만 사용되기 전에 나중에 초기화될 것임을 나타내는 데 사용돼요. 값이 확실히 들어올 것을 알지만 당장은 아닐 때, 변수를 nullable(?)로 만들 필요를 없애주는 데 도움이 돼요.
late를 사용하면 초기화가 지연되어, 특히 의존성이나 복잡한 설정을 다룰 때 더 유연하고 읽기 쉬운 코드를 작성할 수 있어요.
예를 들어:
late String description;
void setup() {
description = 'This will be initialized before use.';
}
public API의 일부인 late 변수에는 주의하세요. 클라이언트가 초기화되기 전에 변수에 접근하면, 컨텍스트가 거의 없는 LateInitializationError를 만나게 돼요. 이런 경우에는 너무 일찍 접근하면 설명적인 오류(예: StateError)를 던지는 public getter와 함께 private nullable 변수를 사용하는 것을 고려해 보세요. 약간의 복잡함이 추가되지만 API 사용자에게 더 명확한 피드백을 줄 수 있어요.
변수가 한 번만 설정되어야 한다면 late final을 사용할 수도 있어요. 이는 객체 그래프의 순환 의존성처럼 객체 생성 시점에 값을 사용할 수 없는 시나리오에서 유용해요.
예시:
class LinkedQueue<T> {
late final QueueLink<T> _head;
LinkedQueue() {
_head = QueueLink<T>._head(owner: this); // Cyclic reference between objects
}
}
주의하세요: late 변수가 초기화되기 전에 접근되거나 전혀 초기화되지 않으면 런타임 오류가 발생해요.
관련 문서와 자료
Library
주 Dart 파일과 그 파트들로 구성된, Dart의 단일 컴파일 유닛이에요.
Dart library(라이브러리)는 Dart의 단일 컴파일 유닛으로, 주 .dart 파일과 선택적인 개수의 파트(parts)(part file)로 구성돼요. 라이브러리는 자신만의 private 스코프를 가져요.
관련 문서와 자료
Library-private
정의된 라이브러리(library)로 한정된 접근성.
Dart에서 프라이버시는 클래스가 아니라 라이브러리 수준으로 범위가 정해져요. _myVariable처럼 밑줄로 시작하는 모든 식별자는 라이브러리 private이에요.
관련 문서와 자료
Lockfile
각 의존성의 버전을 명시하는 pubspec.lock 파일.
pubspec.lock이라는 파일로, 패키지가 의존하는 모든 직접(immediate) 의존성과 전이(transitive) 의존성의 구체적인 버전과 기타 식별 정보를 명시해요.
직접 의존성만 나열하고 버전 범위를 허용하는 pubspec(pubspec)과 달리, lockfile은 전체 의존성 그래프를 특정 패키지 버전으로 포괄적으로 고정해요. lockfile은 애플리케이션이 사용하는 패키지의 정확한 구성을 재현할 수 있게 보장해요.
lockfile은 pub get(pub get), pub upgrade(pub upgrade), 또는 pub downgrade(pub downgrade)을 실행할 때 pub이 자동으로 생성해요. pub은 향후 해석 시 확인할 수 있도록 각 의존성에 대한 콘텐츠 해시(content hash)(content hash)를 포함시켜요.
패키지가 애플리케이션 패키지(application package)라면, 보통 이 파일을 소스 제어에 포함시켜요. 일반(라이브러리) 패키지라면 보통 포함시키지 않아요.
관련 문서와 자료
Mixin application
mixin이 클래스에 적용될 때 생성되는 클래스예요.
mixin application(믹스인 애플리케이션)은 mixin이 클래스에 적용될 때 생성되는 클래스예요. 예를 들어, 다음 선언을 생각해 볼게요.
class A {}
mixin M {}
class B extends A with M {}
클래스 B는 M을 A에 적용한 mixin application의 서브클래스로, 때로는 A+M이라고 표기해요. 클래스 A+M은 A의 서브클래스이며 M에서 복사된 멤버들을 가져요.
mixin application에 실제 이름을 부여할 수도 있어요.
class A {}
mixin M {}
class A_M = A with M;
이렇게 A_M을 선언하면, 다음 B의 선언은 원래 예시의 B 선언과 동일해요.
class B extends A_M {}
관련 문서와 자료
Null safety
변수 타입이 명시적으로 허용하지 않는 한 변수가 null을 담을 수 없는, Dart 타입 시스템의 기능이에요.
Dart의 null safety는 null 값의 의도하지 않은 접근으로 인한 오류를 방지하는 데 도움이 되는 언어 기능이에요.
null safety를 사용하면 타입은 기본적으로 non-nullable이에요: ? 접미사로 타입을 nullable로 선언하지 않는 한 변수는 null을 담을 수 없어요.
int count = 42; // Can't be null.
int? maybeCount; // Can be null (and is initialized to null by default).
Dart의 null safety는 sound(sound)해요. 타입 시스템이 어떤 변수가 non-nullable 타입임을 결정하면, 그 변수는 런타임에 절대 null이 되지 않도록 보장돼요. 이 soundness 보장은 정적 분석과 런타임 검사가 결합되어 강제되며, 더 작고 효율적인 코드를 만드는 컴파일러 최적화를 가능하게 해줘요.
관련 문서와 자료
Obviously typed
타입이 모호함 없이 기계적으로 결정될 수 있는 표현식.
Dart에서 어떤 표현식은, 추론이나 복잡한 분석 없이 문법만으로 타입이 즉시 명확할 때 obviously typed(명백히 타입이 정해진)로 간주돼요.
이 개념은 주로 omit_obvious_local_variable_types나 specify_nonobvious_property_types 같은 린트 규칙에서, 명시적 타입 애너테이션이 중복인지 필요한지 판단하는 데 사용돼요.
Obviously typed 표현식
다음 유형의 표현식이 obviously typed로 간주돼요.
- 비컬렉션 리터럴:
1,true,'Hello'. - 명시적 타입 인자를 가진 컬렉션 리터럴:
<int>[],<String, bool>{}. - 요소가 obviously typed인 동종(homogeneous) 리스트/셋 리터럴:
[1, 2, 3],{true, false}. - 키와 값 타입이 명백한 동종 맵 리터럴:
{1: 10, 2: 20}. - 타입이 명백한 레코드 리터럴:
(1, enabled: true). - 프로모션되지 않은 지역 변수와 형식 매개변수.
- 제네릭이 아닌 클래스나 명시적 타입 인자를 사용한 인스턴스 생성:
C(),Set<int>(). - 타겟이 obviously typed인 캐스케이드:
StringBuffer('Hello, ')..write('world!'). - 타입 캐스트:
x as int. - 같은 타입의 obviously typed 분기를 가진 조건 표현식:
condition ? 1 : 2. - 타입 테스트:
x is String. - throw 표현식:
throw Exception(). - 내용이 obviously typed인 괄호 포함 표현식:
('Hello!'). this와 타입 리터럴:String.
이 목록에서 homogeneous(동종)은 "모든 요소가 같은 타입"이라는 뜻이에요. 예를 들어 {1: 'one', 2: 'two'}는 동종 맵이지만, {1: 'one', 1.5: true}는 아니에요. 반대로, 표현식의 타입에 추론이 필요하다면(예: [1, 2.5] → List<num>), 이는 obviously typed로 간주되지 않아요.
이 구분은 스타일 규칙이 명확성과 일관성을 위해 언제 타입 애너테이션을 강제하고 생략할지 결정하는 데 도움을 줘요.
관련 문서와 자료
- Effective Dart: Style
- Lint: omit_obvious_local_variable_types
- Lint: specify_nonobvious_property_types
Override inference
메서드 선언에서 누락된 타입이 어떻게 추론되는지를 말해요.
override inference는 메서드 선언에서 누락된 타입이, 그 메서드가 오버라이드하는 메서드(들)의 대응 타입을 기반으로 추론되는 과정이에요.
후보 메서드(타입 정보가 누락된 메서드)가 단일 상속 메서드를 오버라이드한다면, 오버라이드된 메서드의 대응 타입이 추론돼요. 예를 들어, 다음 코드를 생각해 볼게요.
class A {
int m(String s) => 0;
}
class B extends A {
@override
m(s) => 1;
}
B의 m 선언은 반환 타입과 매개변수 타입이 모두 누락되어 있으므로 후보예요. 단일 메서드(A의 m)를 오버라이드하기 때문에, 오버라이드된 메서드의 타입이 누락된 타입을 추론하는 데 사용되고, 마치 B의 메서드가 int m(String s) => 1;로 선언된 것처럼 돼요.
후보 메서드가 여러 메서드를 오버라이드하고, 그 오버라이드된 메서드 중 하나의 함수 타입 Ms가 다른 모든 오버라이드된 메서드의 함수 타입의 수퍼타입이라면, Ms를 사용해 누락된 타입을 추론해요. 예를 들어 다음 코드를 생각해 볼게요.
class A {
int m(num n) => 0;
}
class B {
num m(int i) => 0;
}
class C implements A, B {
@override
m(n) => 1;
}
C의 m 선언은 반환 타입과 매개변수 타입이 모두 누락되어 있으므로 override inference의 후보예요. A의 m과 B의 m을 둘 다 오버라이드하므로, 컴파일러는 누락된 타입을 추론할 기준 중 하나를 선택해야 해요. A의 m 함수 타입(int Function(num))이 B의 m 함수 타입(num Function(int))의 수퍼타입이므로, A의 함수가 누락된 타입을 추론하는 데 사용돼요. 결과는 C의 메서드를 int m(num n) => 1;로 선언한 것과 같아요.
오버라이드된 메서드 중 어떤 것도 다른 모든 오버라이드된 메서드의 수퍼타입인 함수 타입을 갖지 않는다면, 이는 오류예요.
관련 문서와 자료
Package
Dart 라이브러리와 리소스의 모음, 그리고 그것들을 설명하는 pubspec.yaml 파일이 있는 디렉토리예요.
Dart package(패키지)는 디렉토리 안의 Dart 라이브러리(libraries)와 리소스의 모음으로, 해당 디렉토리 루트에 pubspec.yaml(pubspec.yaml) 파일이 있어요.
패키지는 다른 패키지에 대한 의존성(dependencies)(dependencies)을 가질 수 있고, 그 자체가 의존성이 될 수도 있어요. 패키지의 /lib 디렉토리는 다른 패키지가 import 하고 사용할 수 있는 public 라이브러리(public libraries)를 담고 있어요. 직접 실행할 스크립트를 포함할 수도 있어요. 다른 패키지가 의존하지 않도록 만들어진 패키지는 애플리케이션 패키지(application package)예요. 공유 패키지는 pub.dev에 게시(published)되지만, 게시되지 않은 패키지도 가질 수 있어요.
패키지의 lockfile(lockfile)은 소스 제어에 포함시키지 마세요. 라이브러리는 의존성 버전 범위를 지원해야 하기 때문이에요. 패키지의 직접 의존성(immediate dependencies)의 버전 제약(version constraints)은, 의존성이 테스트된 버전과 호환되도록 보장하면서 가능한 한 넓어야 해요.
semantic versioning(semantic versioning)은 라이브러리가 하위 호환되지 않는 변경에 대해 주 버전을 올리도록 요구하므로, 패키지는 보통 의존성 버전이 테스트된 버전보다 크거나 같고, 다음 주 버전보다 작도록 요구해요. 그래서 라이브러리가 (가상의) transmogrify 패키지에 의존하고 1.2.1 버전에서 테스트했다면, 버전 제약은 ^1.2.1이 돼요.
관련 문서와 자료
Package uploader
패키지에 대한 관리 권한을 가진 pub.dev 사용자예요.
package uploader(패키지 업로더)는 패키지에 대한 관리 권한을 가진 사람이에요. 패키지 업로더는 패키지의 새 버전을 업로드할 수 있고, 그 패키지의 다른 업로더를 추가하거나 제거할 수 있어요(add and remove other uploaders).
패키지에 검증된 publisher(verified publisher)가 있다면, publisher의 모든 멤버가 패키지를 업로드할 수 있어요.
관련 문서와 자료
Part file
part of 지시문을 포함하는 Dart 소스 파일이에요.
part file(파트 파일)은 part of 지시문을 포함하고, part 지시문을 사용해 라이브러리에 포함되는 Dart 소스 파일이에요.
관련 문서와 자료
Potentially constant
포함하는 상수 생성자의 모든 생성자 매개변수가 상수 값으로, 모든 타입 매개변수가 구체적인 타입으로 대체되면 컴파일 타임에 평가될 수 있는 표현식이에요.
potentially constant expression(잠재적으로 상수인 표현식)은 상수가 요구되는 컨텍스트(예: 상수 생성자의 초기화 리스트)에서 유효한 표현식이에요. 생성자 매개변수나 타입 매개변수를 참조하기 때문에 그 자체로는 컴파일 타임 상수가 아닐 수 있지만요.
포함하는 상수 생성자의 모든 생성자 매개변수가 적절한 타입의 상수 값으로, 모든 타입 매개변수가 구체적인 타입으로 대체되면, potentially constant 표현식은 컴파일 타임 상수 표현식이 돼요.
예를 들어, 다음 클래스에서:
class ConstPoint {
final int x;
final int y;
const ConstPoint(int a) : x = a, y = a + 1;
}
초기화 표현식 a와 a + 1은 potentially constant 표현식이에요. a의 값이 생성자를 어떻게 호출하느냐에 달려 있으므로 상수 표현식은 아니에요.
하지만 생성자를 const ConstPoint(3)으로 호출하면, 매개변수 a가 상수 3으로 대체되어 3과 3 + 1이 상수 표현식이 돼요.
관련 문서와 자료
Potentially non-nullable
명시적으로 non-nullable이거나 타입 매개변수이기 때문에 non-nullable일 수 있는 타입이에요.
타입이 potentially non-nullable(잠재적으로 non-nullable)이라는 것은 명시적으로 non-nullable이거나 타입 매개변수라는 뜻이에요.
물음표(?)가 뒤에 오지 않는 타입 이름이면 명시적으로 non-nullable이에요. Null과 dynamic처럼 항상 nullable인 타입이 몇 가지 있고, FutureOr는 물음표가 뒤에 오지 않으면서 그리고 타입 인자가 non-nullable(예: FutureOr<String>)일 때만 non-nullable이라는 점을 주의하세요.
타입 매개변수는 실제 런타임 타입(타입 인자로 지정된 타입)이 non-nullable일 수 있으므로 potentially non-nullable이에요. 예를 들어, class C<T> {} 선언이 주어지면 타입 C는 C<int>처럼 non-nullable 타입 인자와 함께 사용될 수 있어요.
관련 문서와 자료
Primary constructor
클래스, enum, 또는 extension type의 헤더에 메인 생성자를 직접 선언하는 간결한 문법이에요.
primary constructor(프라이머리 생성자)는 클래스, enum, 또는 extension type의 정의 헤더에 메인 생성자를 직접 선언하는 단축 문법이에요.
프라이머리 생성자는 헤더에 직접 매개변수(final 또는 var)와 초기화 formal(this.field)을 선언할 수 있게 해서, 매개변수 선언과 필드 초기화의 보일러플레이트를 줄여줘요.
var 또는 final 수정자로 매개변수를 선언하면 해당 필드가 암시적으로 생성되고 초기화돼요:
class Point(var int x, var int y);
관련 문서와 자료
Private named parameter
선언에서는 private 이름을, 호출 지점에서는 public 이름을 가지는 이름 있는 생성자 매개변수예요.
private named parameter(private 이름 매개변수)는 이름이 private인 이름 매개변수예요.
이 매개변수는 초기화 매개변수(initializing, 예: this._x) 또는 선언 매개변수(declaring, 예: final T _x 또는 var T _x)일 때만 허용돼요. 초기화 매개변수는 전통적 생성자나 프라이머리 생성자에 나타나고, 선언 매개변수는 프라이머리 생성자에만 나타나요.
초기화 매개변수는 주어진 private 이름으로 인스턴스 변수를 초기화해요. 선언 매개변수는 주어진 private 이름으로 인스턴스 변수를 암시적으로 선언하고 초기화해요.
호출 지점에서 인자는 해당하는 public 이름(앞의 밑줄 생략)을 지정해 private 이름 매개변수로 전달돼요:
class Point {
final double _x;
final double _y;
// Callers use the public names 'x' and 'y'.
Point.named({required this._x, required this._y});
}
void main() {
var p = Point.named(x: 10, y: 20); // Initializes '_x' and '_y'.
}
관련 문서와 자료
Pub content hash
패키지 무결성을 검증하기 위해 pub.dev가 유지하는 SHA256 해시.
pub.dev 저장소는 호스팅하는 각 패키지의 각 버전에 대한 SHA256 content hash(콘텐츠 해시)를 유지해요. pub 클라이언트는 이 해시를 사용해 다운로드한 패키지의 무결성을 검증하고, 소스 저장소의 변경으로부터 보호해요.
dart pub get이 패키지를 다운로드하면, 다운로드한 아카이브의 해시를 계산해요. 각 호스팅된 의존성의 해시는 해석(resolution)(resolution)과 함께 lockfile(lockfile)에 저장돼요.
pub 클라이언트는 이 콘텐츠 해시를 사용해, 같은 lockfile로(어쩌면 다른 컴퓨터에서) dart pub get을 다시 실행했을 때 완전히 같은 패키지를 사용하는지 확인해요.
잠긴 해시가 pub 캐시의 현재 내용과 일치하지 않으면, pub은 아카이브를 다시 다운로드해요. 그래도 일치하지 않으면 lockfile이 갱신되고 경고가 출력돼요.
불일치가 경고가 아니라 오류가 되게 하려면, dart pub get에 --enforce-lockfile(--enforce-lockfile) 옵션을 사용하세요. 이 옵션을 쓰면 pub이 같은 해시를 가진 패키지 아카이브를 찾을 수 없을 때 의존성 해석이 실패하고 lockfile은 갱신되지 않아요.
관련 문서와 자료
Pub system cache
pub이 다운로드한 원격 패키지를 저장하는 디렉토리예요.
pub이 원격 패키지를 가져오면, 단일 pub system cache(pub 시스템 캐시) 디렉토리에 다운로드해요. macOS와 Linux에서 이 디렉토리는 기본적으로 ~/.pub-cache예요. Windows에서 디렉토리는 기본적으로 %LOCALAPPDATA%\Pub\Cache인데, 정확한 위치는 Windows 버전에 따라 다를 수 있어요. PUB_CACHE(PUB_CACHE) 환경 변수를 사용해 다른 위치를 지정할 수도 있어요.
패키지가 시스템 캐시에 들어가면, pub은 애플리케이션이 사용하는 각 패키지를 캐시의 대응 패키지에 매핑하는 package_config.json 파일을 만들어요.
주어진 패키지 버전은 한 번만 다운로드하면 되고, 원하는 만큼 많은 패키지에서 재사용할 수 있어요. 캐시된 패키지를 사용하도록 --offline 플래그를 지정하면, 네트워크에 접근하지 않고도 package_config.json 파일을 삭제하고 재생성할 수 있어요.
관련 문서와 자료
Pub workspace
의존성 제약을 공유하는 해석으로 함께 개발되는 패키지 모음이에요.
pub workspace(pub 워크스페이스)는 개발 중에 단일 단위로 취급되는 로컬 패키지 모음으로, 의존성 제약의 공유 해석(shared resolution)을 가능하게 해요. 모노레포에서 개발할 때 유용해요.
패키지들은 워크스페이스 루트 디렉토리에 공유된 pubspec.lock과 .dart_tool/package_config.json 파일을 가져요.
관련 문서와 자료
Public library
패키지의 lib 디렉토리 안에 있지만 lib/src 디렉토리 안에는 없는 라이브러리예요.
public library(공개 라이브러리)는 패키지의 lib 디렉토리 안에 있지만 lib/src 디렉토리 안에는 없는 라이브러리예요.
관련 문서와 자료
Quick fix
특정 진단이 보고한 이슈를 고치는 것을 목표로 하는 자동화된 로컬 코드 편집이에요.
관련 문서와 자료
Refactor
비로컬이거나 사용자 상호작용이 필요한 수정을 목표로 하는 코드 편집이에요.
refactor(리팩터)는 비로컬(non-local)이거나 사용자 상호작용이 필요한 수정을 목표로 하는 코드 편집이에요. 리팩터의 예로는 코드 이름 바꾸기(rename), 제거하기(remove), 추출하기(extract) 등이 있어요.
관련 문서와 자료
Refutable pattern
값에 대해 테스트할 수 있는 패턴이에요.
refutable pattern(반박 가능 패턴)은 값에 대해 테스트해서 패턴이 그 값과 매칭되는지 결정할 수 있는 패턴이에요. 매칭되지 않으면 패턴은 매칭을 반박(refute) 하거나 거부해요. 반박 가능 패턴은 매칭 컨텍스트(matching contexts)(matching contexts)에 나타나요.
관련 문서와 자료
Reified generics
런타임에 제네릭 타입 인자를 보존해서, is List<String> 같은 전체 제네릭 타입에 대한 타입 검사를 가능하게 하는 것.
Dart에서 제네릭 타입 인자는 reified(구체화)되는데, 이는 런타임에 타입 인자를 추적한다는 뜻이에요. 이를 통해 객체의 전체 제네릭 타입을 검사하고 확인할 수 있으며, 타입 인자 없는 원시 타입만 확인하는 것이 아니에요.
예를 들어, 리스트가 구체적으로 List<String>의 서브타입(subtype)인지 확인할 수 있어요:
final names = ['Dart', 'Flutter', 'Jaspr'];
print(names is List<String>); // Prints: true
print(names is List<int>); // Prints: false
Reified generics는 또 Dart의 공변(covariant) 제네릭 타입에 대한 런타임 타입 검사를 가능하게 해요. soundness(soundness)를 보장하기 위해, 리스트에 요소를 추가할 때 Dart는 그 요소가 리스트의 실제 타입 인자와 일치하는지 런타임에 검증해요. 예를 들어 List<int>에 double을 추가하려고 하면, double은 int의 서브타입이 아니므로 Dart는 런타임에 오류를 던져요:
List<num> numbers = <int>[1, 2, 3]; // Explicitly typed as `List<num>`.
numbers.add(1.5); // Throws a TypeError because `1.5` isn't an int.
이는 Java처럼 타입 소거(type erasure) 로 실행 전에 타입 인자를 제거하는 일부 다른 프로그래밍 언어와 대조적이에요. 그래서 그런 언어에서는 객체가 List인지 테스트할 수는 있어도 List<String>인지 테스트할 수는 없어요.
관련 문서와 자료
Scope
이름(변수, 함수, 클래스 등)이 보이고 참조될 수 있는 프로그램의 영역이에요.
Scope(스코프)는 변수, 매개변수, 함수, 클래스 같은 식별자가 프로그램 내에서 접근 가능한 곳을 정의해요.
Dart는 어휘적(정적) 스코프(lexical (static) scoping) 를 사용해요. 이는 이름의 스코프가 프로그램이 런타임에 어떻게 실행되는지가 아니라 소스 코드의 구조에 의해 결정된다는 뜻이에요. 자세한 내용은 lexical scope(lexical scope)를 참고하세요.
Dart에서 흔한 스코프 종류는 다음과 같아요.
- 클래스 스코프: 클래스 내에서 접근 가능한 멤버.
- 블록 스코프:
if,for,while같은 블록 안에서 선언된 변수. - 함수 스코프: 함수 안의 매개변수와 지역 변수.
- 라이브러리 스코프: 라이브러리 내에서 보이는 최상위 선언.
관련 문서와 자료
Shadowing
로컬 선언이 같은 이름의 다른 선언을 가릴 때를 말해요.
Shadowing(섀도잉)은 변수나 매개변수 같은 로컬 선언이 바깥 스코프의 기존 선언과 같은 이름을 사용해서, 안쪽 스코프에서 바깥쪽 선언에 접근할 수 없게 될 때 발생해요.
Dart에서 유효하지만, 섀도잉은 혼란스러운 코드나 의도하지 않은 동작을 초래할 수 있어요. 그래서 보통 코드의 명확성을 높이기 위해 의도적으로 사용하지 않는 한 권장되지 않아요.
예시
이 예시에서 printMessage 함수 안의 로컬 message 변수는 최상위 message 변수를 섀도우(가림) 해요:
final message = 'Global';
void printMessage() {
final message = 'Local'; // Shadows the global `message` variable.
print(message); // Prints: Local
}
void main() {
printMessage();
print(message); // Prints: Global
}
섀도잉은 중첩된 블록에서도 발생할 수 있어요:
void main() {
final value = 10;
if (true) {
final value = 20; // Shadows the outer `value` variable.
print(value); // Prints: 20
}
print(value); // Prints: 10
}
Sound
표현식의 런타임 값이 항상 정적 타입과 일치한다는 타입 시스템의 보장이에요.
타입 시스템이 표현식이 정적 타입과 일치하지 않는 값으로 평가되는 상태에 프로그램이 절대 도달할 수 없음을 보장하면 sound(건전)하다고 해요. 예를 들어, 표현식의 정적 타입이 String이면, 런타임 값이 평가될 때 String일 것이 보장돼요.
Dart의 타입 시스템은 sound해요. 정적 검사(컴파일 타임 오류)와 런타임 검사가 결합되어 이 보장을 강제해요.
- **정적 검사(Static checks)**는 대부분의 타입 오류를 컴파일 타임에 잡아요. 예:
int타입 변수에String을 할당하는 것. - **런타임 검사(Runtime checks)**는 컴파일러가 정적으로 확인할 수 없는 경우를 처리해요. 예:
as를 이용한 캐스트와 공변 제네릭 타입 인자.
Soundness는 실질적인 여러 이점을 줘요.
- 타입 관련 버그가 컴파일 타임에 드러나요. 건전한 타입 시스템은 코드가 타입에 대해 모호하지 않도록 강제하므로, 런타임에 찾기 까다로운 타입 관련 버그를 일찍 잡을 수 있어요.
- 더 읽기 쉬운 코드. 값이 실제로 선언된 타입을 갖는다는 것을 믿을 수 있어요. sound한 Dart에서 타입은 거짓말하지 않아요.
- 더 유지보수하기 쉬운 코드. 코드의 한 부분을 바꾸면, 건전한 타입 시스템 덕분에 Dart 도구가 망가진 다른 코드에 대해 경고해 줄 수 있어요.
- 더 나은 ahead-of-time(AOT)(ahead-of-time (AOT)) 컴파일. sound한 타입은 컴파일러가 더 작고 효율적인 네이티브 코드를 생성하는 데 도움을 줘요.
Soundness가 Dart가 모든 버그를 잡는다는 뜻은 아니에요. 논리 오류, off-by-one 실수, 타입 시스템이 다루지 않는 다른 문제가 있는 코드를 여전히 작성할 수 있어요. 단지 타입 자체는 항상 신뢰할 수 있다는 뜻이에요.
관련 문서와 자료
Subclass
다른 클래스의 구현을 상속하는 클래스예요.
subclass(서브클래스)는 extends(extends) 키워드나 mixin application(mixin application)을 사용해 다른 클래스의 구현을 상속하는 클래스예요.
// A is a subclass of B; B is the superclass of A.
class A extends B {}
// B1 has the superclass `A with M`, which has the superclass A.
class B1 extends A with M {}
서브클래스 관계는 연관된 서브타입(subtype) 관계도 암시해요. 예를 들어, class A는 클래스 A의 인스턴스들이 채우는 연관된 타입 A를 암시적으로 정의해요. 그래서 class A extends B는 클래스 A가 B의 서브클래스임을 선언할 뿐만 아니라, 타입 A가 타입 B의 서브타입임도 확립해요.
서브클래스 관계는 서브타입 관계의 부분집합이에요. 문서가 "S는 T의 서브타입이어야 한다"고 말할 때, S가 T의 서브클래스여도 괜찮아요. 하지만 그 역은 성립하지 않아요: 모든 서브타입이 서브클래스는 아니에요.
관련 문서와 자료
Subtype
수퍼타입의 값이 기대되는 곳 어디든 사용될 수 있는 타입이에요.
subtype(서브타입) 관계는 어떤 타입의 값이 다른 타입인 수퍼타입의 값이 기대되는 곳에서 대체 가능한 관계예요. 예를 들어, S가 T의 서브타입이면, T의 값이 기대되는 곳에 S 타입의 값을 대체할 수 있어요.
서브타입은 수퍼타입의 모든 연산(그리고 어쩌면 추가 연산)을 지원해요. 실질적으로 이는 서브타입의 값을 수퍼타입이 기대되는 어떤 위치에도 할당할 수 있고, 수퍼타입의 모든 메서드가 서브타입에서 사용 가능하다는 뜻이에요.
이것은 적어도 정적으로는 성립해요. 특정 API는 그 연산에 따라 런타임에 대체를 허용하지 않을 수도 있어요.
일부 서브타입 관계는 타입의 구조에 기반해요, 예를 들어 nullable 타입(int는 int?의 서브타입)과 함수 타입(String Function()은 void Function()의 서브타입)이 그렇죠.
서브타입은 구현(implementation)이나 상속(inheritance)(직접 또는 간접)을 통해 클래스에 도입될 수도 있어요:
// A is a subtype of B, but NOT a subclass of B.
class A implements B {}
// C is a subtype AND a subclass of D.
class C extends D {}
관련 문서와 자료
Super parameter
수퍼클래스 생성자에 인자를 자동으로 전달하는 생성자 매개변수예요.
super parameter(슈퍼 매개변수)는 서브클래스 생성자에서 수퍼클래스 생성자로 매개변수를 전달하는 것을 단순화하는 단축 문법이에요.
초기화 리스트에서 매개변수를 수동으로 선언하고 전달하는 대신, 매개변수 이름 앞에 super.을 붙일 수 있어요.
예를 들어, Hammer({required super.price})에서 price 값은 수퍼클래스 생성자로 직접 전달돼요.
관련 문서와 자료
Top type
Object?를 포함해 다른 모든 타입의 수퍼타입인 타입이에요.
top type(탑 타입)은 Object?의 수퍼타입인 어떤 타입이든 가리켜요. Object? 자체가 다른 모든 Dart 타입의 수퍼타입이므로, 탑 타입은 타입 계층의 맨 꼭대기에 있어요. 그래서 어떤 값이든 탑 타입 변수에 할당할 수 있어요.
Dart에는 많은 탑 타입이 있지만, 주요 탑 타입은 다음 세 가지예요.
Object?— 모든 타입의 nullable 수퍼타입.null을 포함한 Dart의 모든 값은Object?예요.dynamic— 정적 타입 검사를 비활성화하는 특수 타입.Object?처럼 어떤 값이든 받아들이지만, 컴파일 타임 오류 없이 어떤 멤버에든 접근할 수 있게 해줘요. 검사는 대신 런타임으로 미뤄져요.void— 값이 사용될 의도가 없음을 나타내는 타입.void에 어떤 값이든 할당할 수 있지만, 캐스트 없이는 그 결과를 사용할 수 없어요.
이 주요 세 가지 외에도 다른 복합 탑 타입이 있어요. FutureOr<T>는 T가 탑 타입일 때마다 탑 타입이고, 특정 타입에 ?를 붙여도 탑 타입이 만들어져요. 예를 들어 FutureOr<Object?>, FutureOr<Object>?, dynamic?은 모두 탑 타입이에요.
Object?와 대조적으로, non-nullable Object 타입은 모든 non-nullable 타입의 수퍼타입이지만, Null의 수퍼타입이 아니므로 진정한 탑 타입은 아니에요.
탑 타입은 바텀 타입(bottom type)의 반대 개념이에요. 탑 타입이 모든 것의 수퍼타입인 반면, 바텀 타입(Dart의 Never)은 모든 것의 서브타입이에요.
Object? a = 42; // OK: int is a subtype of Object?.
Object? b = 'hello'; // OK: String is a subtype of Object?.
Object? c = null; // OK: Null is a subtype of Object?.
dynamic d = [1, 2, 3]; // OK: List<int> is a subtype of dynamic.
void e = true; // OK: bool is a subtype of void.
관련 문서와 자료
- The Dart type system
- Bottom type
- Top and bottom types in Dart
- Syntactic characterization of top types in Dart
- Top type on Wikipedia
Transitive dependency
패키지가 의존성 중 하나 때문에 간접적으로 사용하는 의존성이에요.
transitive dependency(전이 의존성)는 패키지가 그 의존성 중 하나(또는 그들의 의존성) 때문에 간접적으로 사용하는 의존성(dependency)이에요.
패키지가 A에 의존하고, A가 B에 의존하며, B가 C에 의존한다면, A는 직접 의존성(immediate dependency)이고 B와 C는 전이 의존성이에요.
관련 문서와 자료
Tree shaking
사용하지 않는 코드를 제거하는 컴파일러 최적화예요.
Tree shaking(트리 셰이킹)은 코드의 어떤 부분을 애플리케이션이 실제로 사용하는지 분석하고, 최종 출력에서 그 밖의 모든 것을 제거하는 컴파일러 최적화예요.
이렇게 하면 참조되지 않는 죽은 코드가 바이너리나 번들에 들어가지 않으므로, 컴파일된 프로그램이 더 작아지고 로드도 빨라져요.
트리 셰이킹은 코드 크기가 다운로드 시간과 런타임 성능에 직접 영향을 주는 웹·모바일 앱에서 특히 중요해요.
예를 들어, 열 개의 최상위 함수를 정의하는 라이브러리를 import 하지만 앱이 두 개만 호출한다면, 트리 셰이킹은 나머지 여덟 개의 함수가 컴파일된 출력에서 제외되도록 보장해요.
관련 문서와 자료
Type alias
기존 타입에 대한 사용자 정의 이름이에요.
type alias(타입 별칭)는 다른 타입을 가리키는 대체 이름이에요.
복잡한 타입 정의를 단순화하고, 가독성을 높이며, 코드에 의미론적 의미를 부여하는 데 사용할 수 있어요.
Dart는 typedef 키워드를 사용해 타입 별칭 정의를 지원해요. 함수, 클래스, 심지어 제네릭 타입도 별칭으로 만들 수 있어요.
예시
함수 타입 별칭
typedef StringTransformer = String Function(String);
void printTransformed(String input, StringTransformer transformer) {
print(transformer(input));
}
void main() {
printTransformed('hello', (str) => str.toUpperCase()); // Output: HELLO
}
클래스 별칭
class HttpClient {}
typedef Client = HttpClient;
Client client = HttpClient();
타입 별칭은 새 타입을 만들지 않고, 대체 이름을 제공할 뿐이에요.
관련 문서와 자료
Uninitialized
선언됐지만 아직 값을 할당받지 않은 변수예요.
변수가 선언됐지만 아직 값을 할당받지 않았으면 uninitialized(초기화되지 않음) 상태예요.
non-nullable 변수는 읽히기 전에 초기화되어야 해요. 선언 시점에 하거나, 첫 사용 전의 모든 가능한 실행 경로에서 초기화하거나요:
void f() {
int x;
print(x); // Error: variable 'x' must be assigned before it can be used.
}
명시적으로 초기화되지 않은 nullable 변수는 기본적으로 null이에요:
int? count; // Implicitly initialized to null.
print(count); // Prints: null
non-nullable 변수의 초기화를 지연하려면 late 키워드를 사용하세요. late 변수는 첫 할당까지 초기화되지 않은 상태이고, 할당되기 전에 접근하면 런타임에 late initialization 오류가 발생해요:
late String name;
print(name); // Throws a late initialization error if not yet assigned.
관련 문서와 자료
Variance and variance positions
타입의 타입 인자를 바꾸는 것이 원래 타입과 결과 타입 사이의 관계에 어떤 영향을 주는지.
Dart에서 타입 선언(클래스 등)이나 함수 반환 타입의 타입 인자를 바꾸면, 전체 타입 관계가 같은 방향으로 바뀌어요(공변, covariant).
하지만 함수의 매개변수 타입을 바꾸면, 전체 타입 관계가 반대 방향으로 바뀌어요(반변, contravariant).
클래스(또는 mixin 같은 다른 타입 선언)의 타입 매개변수는, 타입 전체가 실제 타입 인자에 따라 "함께 변할(covary)" 때 covariant(공변)이라고 해요. 다시 말해, 타입 인자가 서브타입으로 대체되면 타입 전체도 서브타입이 돼요.
예를 들어, List 클래스의 타입 매개변수는 공변인데, 리스트 타입이 타입 인자와 함께 변하기 때문이에요: int가 Object의 서브타입이므로 List<int>는 List<Object>의 서브타입이에요.
Dart에서 모든 클래스, mixin, mixin class, enum 선언의 모든 타입 매개변수는 공변이에요.
하지만 함수 타입은 달라요: 함수 타입은 반환 타입에서는 공변이고, 매개변수 타입에서는 그 반대(contravariant, 반변)예요. 예를 들어, 타입 int Function(int)는 Object Function(int)의 서브타입이지만, int Function(Object)의 수퍼타입이에요.
이것은 대체 가능성(substitutability)을 생각하면 이치가 맞아요. 정적 타입 int Function(int)로 함수를 호출한다면, 그 함수는 런타임에 실제로는 int Function(Object) 타입일 수 있어요. 정적 타입에 기반해 int를 넘길 수 있을 것이라 기대해요. 함수가 실제로 어떤 Object든 받아들이고 그 안에 int 타입의 모든 객체가 포함되므로 괜찮아요. 마찬가지로, 반환 결과는 정적 타입에 기반해 기대하는 대로 int 타입이 돼요.
따라서 int Function(Object)는 int Function(int)의 서브타입이에요.
매개변수 타입에서는 모든 것이 뒤집힌다는 점을 주의하세요. 특히, 함수 타입 사이의 이 서브타입 관계는 매개변수 타입에 반대 서브타입 관계가 존재해야 함을 요구해요. 예를 들어, int가 Object의 서브타입이므로 void Function(Object)는 void Function(int)의 서브타입이에요.
List<void Function(int)> 같은 더 복잡한 타입에서는 타입 안의 위치(positions) 를 고려해야 해요. 이를 위해 타입의 한 부분을 자리 표시자로 만들고, 그 위치에 서로 다른 타입이 놓일 때 타입에 어떤 일이 일어나는지 생각해 보세요.
예를 들어, List<void Function(_)>를 자리 표시자 _ 자리에 서로 다른 타입을 넣을 수 있는 타입의 템플릿으로 생각해 보세요. 이 타입은 그 자리 표시자가 발생하는 위치에서 반변(contravariant)이에요.
다음은 _에 Object와 int를 대입해서 이를 보여줘요. List<void Function(Object)>는 List<void Function(int)>의 서브타입인데, void Function(Object)가 void Function(int)의 서브타입이기 때문이에요. void가 void의 서브타입이고(반환 타입), int가 Object의 서브타입이니까요(매개변수 타입, 반대 순서). 따라서 _의 타입은 타입 List<void Function(_)> 전체와 반대 방향으로 변하며, 이 '반대 방향'은 정의상 반변 위치(contravariant position) 로 만들어요.
covariant position(공변 위치)도 비슷하게 정의돼요. 예를 들어, _는 타입 List<_>에서 공변 위치에 있고, 타입 _ Function(int)에서도 공변 위치에 있어요.
invariant(불변)로 알려진 또 다른 종류의 위치도 있지만, 훨씬 드물게 발생하므로 여기서는 세부 사항을 생략할게요.
실질적으로는, 클래스·mixin 등의 타입 인자와 함수 타입의 반환 타입은 공변 위치에 있고, 매개변수 타입은 반변 위치에 있다는 것만 알아도 충분한 경우가 많아요.
관련 문서와 자료
Verified publisher
pub.dev에서 신원이 검증된 pub.dev 사이트의 패키지 게시자예요.
verified publisher(검증된 publisher)는 고유 도메인 이름으로 식별되는 한 명 이상의 사용자 모음이에요. 도메인 이름의 소유권은 pub.dev에 의해 검증돼요. 예를 들어 Dart 팀이 pub.dev의 dart.dev publisher(dart.dev publisher)가 그렇죠.
관련 문서와 자료
Version constraint
각 의존성에 연결된, 패키지가 함께 동작할 것으로 예상되는 버전을 지정하는 제약이에요.
version constraint(버전 제약)는 패키지를 위한 의존성(dependency)의 호환 가능한 버전 범위를 지정한 것이에요. 단일 버전(0.3.0)이거나 버전 범위(^1.2.1)일 수 있어요. any도 허용되지만, 성능상 이유로 권장되지는 않아요.
라이브러리 패키지(packages)는 각 non-dev 의존성에 대해 항상 버전 제약을 지정해야 해요. 반면 애플리케이션 패키지(Application packages)는 의존성 버전 관리를 lockfile(lockfile)로 하므로 의존성의 어떤 버전이든 허용할 수 있어요.
관련 문서와 자료
WebAssembly (Wasm)
웹 브라우저에서 코드의 고성능 실행을 가능하게 하는 바이너리 명령어 포맷이에요.
WebAssembly(흔히 Wasm으로 줄임)는 프로그래밍 언어의 컴파일 대상으로 설계된 이식 가능한 바이너리 포맷으로, 웹에서 네이티브에 가까운 실행 속도를 가능하게 해요.
Dart는 코드를 WebAssembly로 직접 컴파일하는 것을 지원해요(dart compile wasm 도구 사용). Dart를 Wasm으로 컴파일하는 것은 특히 Flutter 웹 앱에서 유익할 수 있는데, 표준 JavaScript 컴파일과 비교해 프레임 레이트, 시작 시간, 애플리케이션 크기를 개선해요.
관련 문서와 자료
Wildcard
패턴 및 다른 컨텍스트에서 사용하지 않는 값을 나타내기 위해 변수 이름 대신 사용하는 기호(_)예요.
wildcard(와일드카드)는 값을 무시하거나 값이 의도적으로 사용되지 않음을 나타내기 위해 사용하는 밑줄 문자(_)예요. 패턴, 구조 분해, switch 표현식에서 이름에 바인딩하지 않고 어떤 값과도 매칭하는 데 자주 사용돼요.
와일드카드는 특정 컨텍스트에서 필요하지 않은 값을 명확하게 표시해서 코드를 더 의도적으로 만들어줘요.
예시:
// Ignoring the value in a for-each loop.
var names = ['Alice', 'Bob', 'Charlie'];
for (var _ in names) {
print('Someone is here!');
}
와일드카드 패턴은 특히 다음 경우에 유용해요.
- 구조 분해된 값의 특정 부분만 필요할 때.
- 일부 값이 무시되고 있음을 명시적으로 보여주고 싶을 때.
- 패턴 매칭에서 catch-all 케이스가 필요할 때.
관련 문서와 자료
Zone
비동기 코드 자체를 수정하지 않고 비동기 코드의 동작을 사용자 정의하는 메커니즘이에요.
zone(존)은 타이머, 마이크로태스크, 잡히지 않은 오류 같은 비동기 이벤트에 대해 사용자 정의된 동작으로 코드를 실행할 수 있게 하는 실행 컨텍스트예요.
zone은 다음에 유용해요.
- 로깅
- 오류 추적
- 비동기 격차를 넘어 요청별 상태 유지(예: 서버 앱에서)
- 비동기 동작 테스트와 디버깅
zone은 비동기 코드가 이를 인식하지 않아도 비동기 실행을 추적하고 영향력을 행사할 수 있는 방법을 제공해요.
runZoned(또는 runZonedGuarded)를 사용해 새 zone을 만들고, 오류 처리와 타이머 같은 zone별 동작을 오버라이드할 수 있어요. print조차 오버라이드할 수 있는데, 비동기가 아니지만 편의를 위해 포함된 것이에요.
예시:
import 'dart:async';
void main() {
runZonedGuarded(() {
Future.delayed(Duration(seconds: 1), () {
throw 'Zone caught this error!';
});
}, (error, stackTrace) {
print('Caught error: $error');
});
}
위 예시에서 비동기 콜백 안의 잡히지 않은 오류는 사용자 정의 zone에 의해 가로채져요.
관련 문서와 자료
별도로 명시하지 않는 한, 이 사이트의 문서는 Dart 3.13.3을 기준으로 해요. 이슈 보고하기.
더 알아보기
- Dart 언어 둘러보기 — 핵심 개념들을 더 자세히 배워 보세요.
- pub.dev 용어 이해 — pub 관련 용어를 정리해 보세요.
- Dart null safety — null safety와 관련된 용어들을 깊이 있게 파 보세요.