В
Dart 3.13 появилась фича, которую ждали годами -
primary constructors. Теперь класс с полями и конструктором можно объявить в одну строку. Но путь к этому решению был долгим. Команда
Dart потратила много времени на обсуждение деталей и принятие компромиссов.
Боб Нюстром написал большой разбор о том, через что пришлось пройти. Давайте разберем самые важные моменты из данного разбора.
Почему синтаксический сахар важен:
Primary constructors не добавляют в язык новых возможностей. Все, что можно сделать через них, уже можно было написать старым способом. Это чистый синтаксический сахар.
Вопрос в том, зачем он нужен. Синтаксический сахар оправдан в нескольких случаях.
- Первый - когда новый способ просто лучше старого. Например, в Dart 2.0 сделали необязательным ключевое слово new при создании объектов. Старый синтаксис остался для совместимости, но никто его не использует.
- Второй - когда синтаксис сильно упрощает частый сценарий. В Dart изначально не было enum. Их эмулировали через классы с константами. Когда добавили настоящие enum, код стал не только короче, но и понятнее.
- Третий - когда синтаксис делает намерение более явным. Код в классах с константами не давал понять, что это enum. А настоящий enum сразу говорит о определенном наборе возможных значений.
Зачем нужен primary constructors:
Primary constructors решают конкретную проблему: объявление класса с конструктором, который просто сохраняет параметры в поля.
Без
primary constructors код выглядит так:
class Point {
final int x;
final int y;
Point(int x, int y) : x = x, y = y;
}
С использованием
initializing formals:
class Point {
final int x;
final int y;
Point(this.x, this.y);
}
Но разработчики хотели еще короче. Чтобы можно было просто написать так:
class Point(final int x, final int y);
Основная проблема:
Главная сложность: синтаксический клиф. Это когда ты начинаешь с простого синтаксиса, а потом хочешь добавить маленькое изменение, но оно не вписывается, и ты вынужден переписывать все.
Например, у класса есть
primary constructor с кучей параметров. Потом нужно добавить логирование в конструктор. Если
primary constructor не поддерживает тело, придется переписать все обратно на старый синтаксис.
Чтобы избежать этого, в
Dart добавили блоки
this { ... } внутри класса. Они позволяют добавить тело или инициализатор для
primary constructor без переписывания всего.
class FormatterOptions(
final int indent = 0,
final int pageWidth = 80,
) {
this {
log.write('Created options.');
}
}
Новый синтаксис конструкторов:
Еще одно изменение касается объявления конструкторов. Раньше нужно было повторять имя класса:
class LongClassName {
LongClassName(); // безымянный
LongClassName.create(); // именованный
}
Теперь можно использовать ключевое слово
new:
class LongClassName {
new(); // безымянный
new create(); // именованный
}
Для фабричных конструкторов:
class AnotherLongClass {
factory () { ... } // фабричный
factory create() { ... } // именованный фабричный
}
Это короче, особенно для длинных имен классов. И решает проблему с
typedef и расширениями, где имя класса не всегда доступно.
Важно помнить:
Объявляющие параметры (final int x в primary constructor) работают только в
primary constructor. Их нельзя использовать внутри тела класса. Решение было осознанным: чтобы не усложнять анализ кода и не делать поиск состояния класса труднее.
Есть и необычное сочетание:
const new. На первый взгляд это выглядит противоречиво, потому что раньше
new и
const были взаимоисключающими при создании объектов. Но теперь
new в объявлении конструктора означает лишь «это конструктор», а
const - «этот конструктор должен быть константным». Если смотреть на это так, сочетание перестает быть странным.
Вывод:
Primary constructors в
Dart 3.13 - это не просто очередной синтаксический сахар. Это результат долгого размышления о том, как сделать язык удобнее и проще.
Команда языка учла опыт других языков, добавила защиту от синтаксических клифов и изменила объявление конструкторов на более простое. В итоге код стал короче, , а язык чуть более приятным для повседневной работы.
Это не последнее изменение. Язык продолжает развиваться. Но
primary constructors - хороший пример того, как дизайн языка может быть одновременно эволюционным и продуманным.