完全型システム

Dart 完全型システム(Sound Type System)

核心定義

Dart は完全静的型言語で、「静的コンパイル検査+実行時検証」の二重の仕組みで安全性を担保します。変数に実行時に格納される値は、静的に宣言した型と必ず一致します。

型ルールは強制的に適用されますが、型注釈は省略可能で、コンパイラが自動的に型推論を行います。

コンパイル時の静的解析により、大半の型エラーを事前に検出できます。

完全性(Soundness)とは

コードが不正な型の状態になることは絶対にありません。式の静的型が String の場合、実行時に取得する値は文字列のみとなり、その他の型は混入しません。同系統の言語:Java、C#。

  • コンパイル時にエラーを捕捉:int a = "abc"; を記述すると即時エラーが発生;
  • 実行時にエラーを捕捉:as による強制キャストで型が不一致の場合、例外がスローされます。

4つのメリット

コンパイル段階で型の不具合を発見、再現が難しい本番環境の型クラッシュを事前に回避;

コードの可読性が向上、型情報が開発者を誤認させることがない;

保守性に優れる、一部コードを修正すると、型検査により影響を受ける全箇所が提示される;

AOTコンパイルの実行効率が高い、型情報を活用しコンパイラが最適化された機械語を生成できる。

サンプルコード

動的コレクションが引き起こす静的エラー

// 引数に List<int> を受け取る想定
void printInts(List<int> a) => print(a);

void main() {
  // ジェネリックを指定しないため List<dynamic> と推論
  final list = [];
  list.add(1);
  list.add('2'); // 動的リストは複数の型を混在可能
  printInts(list); 
  // コンパイルエラー:List<dynamic> を List<int> に代入できない
}Code language: Dart (dart)

ジェネリック制約を明示的に記述

void printInts(List<int> a) => print(a);

void main() {
  final list = <int>[]; // ジェネリックにintを明示
  list.add(1);
  // list.add('2'); // コンパイルエラー、文字列は追加不可
  list.add(2);
  printInts(list); // 静的検査に正常通過
}Code language: Dart (dart)

3つの核心ルール

1 メソッドをオーバーライドする際、戻り値型は元の型またはそのサブタイプにする(プロデューサールール)

親クラスメソッドの戻り値はプロデューサーとみなされ、サブクラスはより具体的なサブタイプを返せますが、無関係な型は使用できません。

正常な記述

class Animal {
  Animal get parent => Animal();
}

class HoneyBadger extends Animal {
  @override
  HoneyBadger get parent => HoneyBadger(); // サブタイプなので合法
}Code language: Dart (dart)

不正な記述

class Root {}
class HoneyBadger extends Animal {
  @override
  Root get parent => Root(); // 無関係な型のためコンパイルエラー
}Code language: Dart (dart)

2 メソッドをオーバーライドする際、引数の型は元の型またはそのスーパータイプにする

メソッドの引数はコンシューマーとみなされ、サブクラスは引数の型を広げる(スーパータイプ使用)ことしか許可されず、サブタイプに狭めると型安全性が破壊されます。

正常例(引数をObjectまで広げる)

class Animal {
  void chase(Animal a) {}
}

class HoneyBadger extends Animal {
  @override
  void chase(Object a) {} // ObjectはAnimalのスーパータイプなので合法
}Code language: Dart (dart)

不正例(引数をサブクラスMouseに狭める)

class Mouse extends Animal {}
class Cat extends Animal {
  @override
  void chase(Mouse a) {} // 引数型を狭め静的エラー発生
}

void main() {
  Animal cat = Cat();
  cat.chase(Alligator()); // 理論上Alligatorを渡せてしまい型が不安全になる
}Code language: Dart (dart)

ルール3:動的ジェネリックコレクションを確定型ジェネリックコレクションに直接代入できない

List<dynamic>List<Dog>List<Cat> に直接代入するとコンパイルエラーが発生します。異なるジェネリックコレクション同士に暗黙的な型変換は存在しません。

class Cat {}
class Dog {}

void main() {
  List<Cat> foo = <dynamic>[Dog()]; // 静的コンパイルエラー
  List<dynamic> bar = <dynamic>[Dog(), Cat()]; // 合法、動的リストは複数型を混在可能
}Code language: Dart (dart)

実行時型検証

静的検査だけで全てのケースをカバーできないため、実行時に追加の型チェックが走り、型が不一致すると例外をスローします。

ジェネリック強制キャストによる実行時エラー

class Animal {}
class Dog extends Animal {}
class Cat extends Animal {}

void main() {
  List<Animal> animals = <Dog>[Dog()];
  List<Cat> cats = animals as List<Cat>; 
  // 実行時例外:List<Dog> を List<Cat> にキャストできない
}Code language: Dart (dart)

dynamic からの暗黙的ダウンキャスト(実行時検証)

dynamic 型の変数を特定の型に代入すると暗黙的な型変換が発生し、実行時に型チェックが実行され、不一致の場合はエラーが出力されます。

int assumeString(dynamic object) {
  String string = object; // 実行時にobjectがStringか判定
  return string.length;
}

void main() {
  assumeString("test"); // 正常実行
  assumeString(123);    // 実行時エラー:int を String に変換不可
}Code language: Dart (dart)

暗黙キャストを無効化(厳格型モード)

analysis_options.yaml に厳格キャスト設定を記述し、dynamicからの暗黙的ダウンキャストを禁止します

analyzer:
  language:
    strict-casts: trueCode language: JavaScript (javascript)

型推論 Type Inference

Dart は変数、ジェネリック、メソッド戻り値の型を自動推論します。型情報が不足する場合は既定で dynamic と判定されます。

通常変数の型推論

// Map<String, Object> と自動推論
var arguments = {'argA': 'hello', 'argB': 42};

var x = 3; // x は int と推論
x = 4.0;   // エラー、int に double を代入できない

num y = 3; // num を明示宣言、int / double 両方格納可能
y = 4.0;   // 合法Code language: Dart (dart)

ジェネリックコンストラクタ / コレクションの推論

List<int> listOfInt = []; // <int>[] と自動推論
var listOfDouble = [3.0]; // List<double> と推論

var ints = listOfDouble.map((x) => x.toInt()); 
// x は double、コールバック戻り値 int → ints は Iterable<int> と推論Code language: Dart (dart)

完全型システム

コメントを残す

メールアドレスが公開されることはありません。 が付いている欄は必須項目です