Звуковая система типов 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)
Звуковая система типов