Звуковая система типов

Звуковая система типов Dart (Sound Type System)

Основное определение

Dart — это язык со статической звуковой типизацией, защищенный двойным механизмом: статическими проверками на этапе компиляции и валидацией во время выполнения. Значение переменной во время работы программы всегда соответствует её статически объявленному типу.

Правила типизации действуют принудительно, но аннотации типов можно опускать — компилятор сам выводит типы через вывод типов.

Статический анализ на этапе компиляции перехватывает подавляющее большинство ошибок типов заранее.

Что такое звуковость (Soundness)

Код никогда не может перейти в невалидное состояние типов. Если статический тип выражения — String, то во время выполнения оно вернёт только строковые значения, другие типы исключены. Похожие языки: Java, C#.

  • Перехват на компиляции: конструкция int a = "abc"; сразу выдаст ошибку;
  • Перехват во время выполнения: при несоответствии типов при приведении через оператор as выбрасывается исключение.

Четыре ключевых преимущества

Ошибки типов обнаруживаются на этапе компиляции, исключая трудновоспроизводимые краши на продакшене из-за несоответствия типов;

Код более читаемый, типы не вводят разработчика в заблуждение относительно структуры данных;

Упрощённая поддержка кода: при изменении одного участка проверка типов подсвечивает все затронутые фрагменты;

Высокая эффективность 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)

Три основные правила

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 строкой
  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, callback возвращает int → ints выводится как Iterable<int>Code language: Dart (dart)

Звуковая система типов

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *