πŸ”’

Dart Type System

Static typing, type inference, generics, typedef

Dart is statically typed with type inference support. Use is/as for type checking and casting, with automatic type promotion after is checks. Generics enable type-safe collections and classes, while typedef lets you name complex function types.

κΈ°λ³Έ 제곡 νƒ€μž…

DartλŠ” 정적 νƒ€μž… μ–Έμ–΄λ‘œ, 컴파일 μ‹œκ°„μ— νƒ€μž… 검사λ₯Ό μˆ˜ν–‰ν•©λ‹ˆλ‹€. μ•„λž˜λŠ” Dartκ°€ 기본으둜 μ œκ³΅ν•˜λŠ” νƒ€μž…λ“€μž…λ‹ˆλ‹€.

int integer = 42; double decimal = 3.14; num number = 10; // int와 double의 μƒμœ„ νƒ€μž… String text = 'μ•ˆλ…•ν•˜μ„Έμš”'; bool flag = true; List<int> numbers = [1, 2, 3]; Map<String, dynamic> person = {'name': '홍길동', 'age': 30}; Set<String> uniqueNames = {'홍길동', 'κΉ€μ² μˆ˜', '이영희'}; Symbol symbol = #symbolName;

특수 νƒ€μž…: void, dynamic, Object

void printMessage() { print('λ©”μ‹œμ§€ 좜λ ₯'); } dynamic dynamicValue = 'λ¬Έμžμ—΄'; dynamicValue = 42; // λ‹€λ₯Έ νƒ€μž… μž¬ν• λ‹Ή κ°€λŠ₯ Object objectValue = 'Hello';

dynamic vs Object

dynamic은 νƒ€μž… 검사λ₯Ό μ™„μ „νžˆ κ±΄λ„ˆλœλ‹ˆλ‹€. ObjectλŠ” λͺ¨λ“  Dart 객체의 μ΅œμƒμœ„ νƒ€μž…μ΄μ§€λ§Œ λ©”μ„œλ“œ 호좜 μ‹œ νƒ€μž… 검사가 μ΄λ£¨μ–΄μ§‘λ‹ˆλ‹€.

νƒ€μž… μΆ”λ‘  (Type Inference)

var둜 μ„ μ–Έν•˜λ©΄ μ΄ˆκΈ°κ°’μ—μ„œ νƒ€μž…μ΄ μžλ™ κ²°μ •λ˜λ©°, 이후 λ‹€λ₯Έ νƒ€μž…μœΌλ‘œ λ³€κ²½ν•  수 μ—†μŠ΅λ‹ˆλ‹€.

var name = '홍길동'; // String으둜 μΆ”λ‘  var age = 30; // int둜 μΆ”λ‘  var height = 175.5; // double둜 μΆ”λ‘  var active = true; // bool둜 μΆ”λ‘  var items = [1, 2, 3]; // List<int>둜 μΆ”λ‘  var getName = () { return '홍길동'; // λ°˜ν™˜ νƒ€μž…λ„ μΆ”λ‘  }; var people = [ // List<Map<String, Object>>둜 μΆ”λ‘  {'name': '홍길동', 'age': 30}, {'name': 'κΉ€μ² μˆ˜', 'age': 25}, ];

νƒ€μž… 체크 & μΊμŠ€νŒ…

is / is! μ—°μ‚°μžλ‘œ νƒ€μž…μ„ ν™•μΈν•˜λ©΄, ν•΄λ‹Ή 블둝 μ•ˆμ—μ„œ μžλ™μœΌλ‘œ νƒ€μž… ν”„λ‘œλͺ¨μ…˜μ΄ μ μš©λ©λ‹ˆλ‹€.

Object value = 'λ¬Έμžμ—΄'; if (value is String) { // 이 블둝 μ•ˆμ—μ„œ μžλ™μœΌλ‘œ String으둜 μΊμŠ€νŒ…λ¨ print('λ¬Έμžμ—΄ 길이: ${value.length}'); } if (value is! int) { print('μ •μˆ˜κ°€ μ•„λ‹™λ‹ˆλ‹€'); }

as μ—°μ‚°μžλŠ” λͺ…μ‹œμ  μΊμŠ€νŒ…μ΄λ©°, μ‹€νŒ¨ μ‹œ λŸ°νƒ€μž„ μ—λŸ¬κ°€ λ°œμƒν•©λ‹ˆλ‹€.

Object value = 'λ¬Έμžμ—΄'; String text = value as String; print(text.toUpperCase()); // int number = value as int; // λŸ°νƒ€μž„ μ—λŸ¬!

νƒ€μž… ν”„λ‘œλͺ¨μ…˜ (Type Promotion)

is 검사 이후 ν•΄λ‹Ή 블둝 μ•ˆμ—μ„œ μžλ™μœΌλ‘œ νƒ€μž…μ΄ μŠΉκ²©λ©λ‹ˆλ‹€. 블둝 λ°”κΉ₯μ—μ„œλŠ” μ›λž˜ νƒ€μž…μœΌλ‘œ λŒμ•„κ°‘λ‹ˆλ‹€.

Object value = 'μ•ˆλ…•ν•˜μ„Έμš”'; if (value is String) { // μžλ™μœΌλ‘œ String으둜 승격 print('λŒ€λ¬Έμž: ${value.toUpperCase()}'); print('길이: ${value.length}'); } // print(value.length); // μ—λŸ¬: Objectμ—λŠ” lengthκ°€ μ—†μŒ

μ œλ„€λ¦­ 클래슀 (Generic Classes)

νƒ€μž…μ„ λ§€κ°œλ³€μˆ˜λ‘œ λ°›μ•„ μž¬μ‚¬μš© κ°€λŠ₯ν•œ 클래슀λ₯Ό μž‘μ„±ν•  수 μžˆμŠ΅λ‹ˆλ‹€.

class Box<T> { T value; Box(this.value); T getValue() { return value; } void setValue(T newValue) { value = newValue; } } void main() { var stringBox = Box<String>('μ•ˆλ…•ν•˜μ„Έμš”'); print(stringBox.getValue()); // 'μ•ˆλ…•ν•˜μ„Έμš”' var intBox = Box<int>(42); print(intBox.getValue()); // 42 var doubleBox = Box(3.14); // Box<double>둜 μΆ”λ‘  }

μ œλ„€λ¦­ ν•¨μˆ˜ (Generic Functions)

T first<T>(List<T> items) { return items.first; } void main() { var names = ['홍길동', 'κΉ€μ² μˆ˜', '이영희']; var firstString = first<String>(names); print(firstString); // '홍길동' var numbers = [1, 2, 3, 4, 5]; var firstInt = first(numbers); // Tκ°€ int둜 μΆ”λ‘  print(firstInt); // 1 }

μ œλ„€λ¦­ νƒ€μž… μ œν•œ (Type Constraints)

extendsλ₯Ό μ‚¬μš©ν•˜μ—¬ μ œλ„€λ¦­ νƒ€μž…μ— μ œν•œμ„ κ±Έ 수 μžˆμŠ΅λ‹ˆλ‹€.

class NumberBox<T extends num> { T value; NumberBox(this.value); void square() { print(value * value); } } void main() { var intBox = NumberBox<int>(10); intBox.square(); // 100 var doubleBox = NumberBox<double>(2.5); doubleBox.square(); // 6.25 // var stringBox = NumberBox<String>('였λ₯˜'); // 컴파일 μ—λŸ¬! }

닀쀑 μ œλ„€λ¦­ λ§€κ°œλ³€μˆ˜ & μ œλ„€λ¦­ 상속

class Pair<K, V> { K first; V second; Pair(this.first, this.second); } // μ œλ„€λ¦­ 클래슀λ₯Ό μƒμ†ν•˜μ—¬ νƒ€μž… κ³ μ • class IntBox extends Box<int> { IntBox(int value) : super(value); void increment() { setValue(getValue() + 1); } } // νƒ€μž… 별칭 typedef StringList = List<String>; typedef KeyValueMap<K, V> = Map<K, V>;

μ»¬λ ‰μ…˜κ³Ό μ œλ„€λ¦­

List, Map, Set은 λͺ¨λ‘ μ œλ„€λ¦­μ„ ν™œμš©ν•˜μ—¬ νƒ€μž… μ•ˆμ „ν•˜κ²Œ μ‚¬μš©ν•  수 μžˆμŠ΅λ‹ˆλ‹€.

// List List<String> names = ['홍길동', 'κΉ€μ² μˆ˜', '이영희']; var fruits = <String>['사과', 'λ°”λ‚˜λ‚˜', 'μ˜€λ Œμ§€']; var numbers = List<int>.filled(5, 0); // [0, 0, 0, 0, 0] var evens = List<int>.generate(5, (i) => i * 2); // [0, 2, 4, 6, 8] var filteredNames = names.where((name) => name.length > 2).toList(); var mappedScores = [90, 85, 95].map((score) => score * 1.1).toList(); // Map Map<String, int> ages = { '홍길동': 30, 'κΉ€μ² μˆ˜': 25, '이영희': 28, }; var config = Map<String, dynamic>(); config['debug'] = true; config['timeout'] = 30; // Set Set<String> uniqueNames = {'홍길동', 'κΉ€μ² μˆ˜', '이영희'}; var colors = <String>{'λΉ¨κ°•', 'νŒŒλž‘', '녹색'}; var nums = Set<int>.from([1, 2, 3, 3, 4]); // {1, 2, 3, 4}

typedef β€” νƒ€μž… 별칭 & ν•¨μˆ˜ νƒ€μž…

λ³΅μž‘ν•œ ν•¨μˆ˜ νƒ€μž…μ΄λ‚˜ νƒ€μž…μ— 이름을 λΆ€μ—¬ν•˜μ—¬ μ½”λ“œ 가독성을 높일 수 μžˆμŠ΅λ‹ˆλ‹€.

// ν•¨μˆ˜ νƒ€μž… 별칭 typedef IntOperation = int Function(int a, int b); int add(int a, int b) => a + b; int subtract(int a, int b) => a - b; void calculate(IntOperation operation, int x, int y) { print('κ²°κ³Ό: ${operation(x, y)}'); } void main() { calculate(add, 10, 5); // κ²°κ³Ό: 15 calculate(subtract, 10, 5); // κ²°κ³Ό: 5 } // Dart 2.13+ 일반 νƒ€μž… 별칭 typedef StringList = List<String>; typedef UserInfo = Map<String, dynamic>; void printNames(StringList names) { for (var name in names) { print(name); } } void displayUserInfo(UserInfo user) { print('이름: ${user[\'name\']}, λ‚˜μ΄: ${user[\'age\']}'); }

Dart 3 νŒ¨ν„΄ λ§€μΉ­κ³Ό νƒ€μž…

Dart 3μ—μ„œλŠ” switch ν‘œν˜„μ‹μ—μ„œ νƒ€μž… 기반 νŒ¨ν„΄ 맀칭이 κ°€λŠ₯ν•©λ‹ˆλ‹€.

Object value = 'λ¬Έμžμ—΄'; switch (value) { case String(): print('λ¬Έμžμ—΄: $value'); case int(): print('μ •μˆ˜: $value'); default: print('기타 νƒ€μž…: $value'); }

μ‹€μ „ 예제: μ œλ„€λ¦­ μΊμ‹œ 클래슀

class Cache<T> { final Map<String, T> _cache = {}; T? get(String key) => _cache[key]; void set(String key, T value) { _cache[key] = value; } bool has(String key) => _cache.containsKey(key); void remove(String key) => _cache.remove(key); void clear() => _cache.clear(); } void main() { var stringCache = Cache<String>(); stringCache.set('greeting', 'μ•ˆλ…•ν•˜μ„Έμš”'); print(stringCache.get('greeting')); // 'μ•ˆλ…•ν•˜μ„Έμš”' var userCache = Cache<Map<String, dynamic>>(); userCache.set('user1', {'name': '홍길동', 'age': 30}); var user = userCache.get('user1'); print('μ‚¬μš©μž: ${user?[\'name\']}, λ‚˜μ΄: ${user?[\'age\']}'); }

μ‹€μ „ 예제: Result νƒ€μž… νŒ¨ν„΄

μ œλ„€λ¦­μ„ ν™œμš©ν•˜μ—¬ 성곡/μ‹€νŒ¨λ₯Ό νƒ€μž… μ•ˆμ „ν•˜κ²Œ μ²˜λ¦¬ν•˜λŠ” Result νŒ¨ν„΄μž…λ‹ˆλ‹€.

abstract class Result<S, E> { factory Result.success(S value) = Success<S, E>; factory Result.failure(E error) = Failure<S, E>; bool get isSuccess; bool get isFailure; S? get value; E? get error; void when({ required void Function(S value) success, required void Function(E error) failure, }); } class Success<S, E> extends Result<S, E> { final S _value; Success(this._value); @override bool get isSuccess => true; @override bool get isFailure => false; @override S get value => _value; @override E? get error => null; @override void when({ required void Function(S value) success, required void Function(E error) failure, }) => success(_value); } class Failure<S, E> extends Result<S, E> { final E _error; Failure(this._error); @override bool get isSuccess => false; @override bool get isFailure => true; @override S? get value => null; @override E get error => _error; @override void when({ required void Function(S value) success, required void Function(E error) failure, }) => failure(_error); }

μ‚¬μš©λ²•:

Result<String, Exception> fetchData() { try { return Result.success('데이터'); } catch (e) { return Result.failure(Exception('였λ₯˜: $e')); } } void main() { var result = fetchData(); result.when( success: (data) => print('성곡: $data'), failure: (error) => print('μ‹€νŒ¨: $error'), ); }

μ°Έκ³ : fpdart νŒ¨ν‚€μ§€

더 ν’λΆ€ν•œ ν•¨μˆ˜ν˜• ν”„λ‘œκ·Έλž˜λ° κΈ°λŠ₯이 ν•„μš”ν•˜λ‹€λ©΄ fpdart νŒ¨ν‚€μ§€λ₯Ό μ°Έκ³ ν•˜μ„Έμš”. Either, Option λ“± κ°•λ ₯ν•œ νƒ€μž…μ„ μ œκ³΅ν•©λ‹ˆλ‹€.

νƒ€μž… μ‹œμŠ€ν…œ 비ꡐ ν‘œ

ν‚€μ›Œλ“œ νƒ€μž… 검사 μš©λ„
var컴파일 νƒ€μž„ μΆ”λ‘ νƒ€μž… μΆ”λ‘  (λ³€κ²½ λΆˆκ°€)
dynamicμ—†μŒ (λŸ°νƒ€μž„)아무 νƒ€μž…μ΄λ‚˜ ν• λ‹Ή κ°€λŠ₯
Object컴파일 νƒ€μž„λͺ¨λ“  νƒ€μž…μ˜ μƒμœ„ νƒ€μž…
is / is!λŸ°νƒ€μž„ μ²΄ν¬νƒ€μž… 확인 + ν”„λ‘œλͺ¨μ…˜
asλŸ°νƒ€μž„ 캐슀트λͺ…μ‹œμ  νƒ€μž… μΊμŠ€νŒ…
T extends X컴파일 νƒ€μž„μ œλ„€λ¦­ νƒ€μž… μ œν•œ

Implementation Steps

1

Type inference β€” var infers type from initial value, cannot reassign to different type

2

is/is! and type promotion β€” inside if (obj is String), String methods are auto-available

3

Generics β€” List<T>, Map<K,V> for type-safe collections, extends for type constraints

4

typedef β€” name complex function types, typedef Compare<T> = int Function(T a, T b)

Pros

  • Compile-time type checking prevents runtime errors
  • Type promotion allows direct use after is check without casting

Cons

  • Overusing dynamic breaks type safety

Use Cases

Designing type-safe API response model classes