Я — Евгений Сатуров, Head of Flutter в Surf и ведущий Flutter Dev Podcast. По традиции представляю перевод официальной статьи про новый релиз языка Dart 2.17, который идёт бок о бок с последним мажорным релизом мультиплатформенного фреймворка Flutter 3.0. Текст дополнен моими комментариями.
На Google I/O мы анонсировали новый Dart SDK версии 2.17. В основу релиза легли важные вопросы повышения продуктивности и портируемости на другие платформы. В этот релиз мы добавили новые языковые фичи: усовершенствовали enum с поддержкой enum-членов и передачу параметров в суперклассы, а также сняли некоторые ограничения для именованных параметров.
Мы улучшили тулинг: выпустили новую мажорную версию package:lints
— инструмента, который проверяет Dart-код на соответствие лучшим практикам — и сделали масштабное обновление документации к основным библиотекам (добавили примеры кода).
Для улучшения интеграции с платформами добавили новые шаблоны для dart:ffi
(взаимодействие с нативным C) во Flutter-плагинах, экспериментальную поддержку процессоров RISC-V, а также поддержку подписи исполняемых файлов для macOS и Windows.
Новые языковые фичи, повышающие продуктивность
В Dart 2.17:
Появилась полнофункциональная поддержка enum-членов.
Изменился подход к работе с именованными аргументами в конструкторах.
Код, с помощью которого параметры передаются в суперклассы, стал более лаконичным и простым.
Улучшенный enum и enum-члены
С помощью перечислений enum удобно представлять дискретные наборы состояний. К примеру, можно смоделировать воду как enum Water { frozen, lukewarm, boiling }
. Но что если нам надо добавить методов к enum
? Скажем, чтобы конвертировать каждое из состояний в температуру или представить enum
в виде String
.
Пожалуй, можно использовать extension-методы и добавить метод waterToTemp()
, но в таком случае нужно быть осторожным и синхронизировать его с enum
. В случае с конвертацией в String
мы бы предпочли переопределить toString()
, но такой возможности раньше не было.
В Dart 2.17 мы добавили поддержку enum-членов. Это значит, что вы можете добавлять:
поля, содержащие состояние;
конструкторы, задающие это состояние;
методы с реализацией или даже переопределять уже существующие методы.
Многие из вас просили добавить такую возможность: она заняла третье место в голосовании за желаемые фичи языка.
В примере с водой теперь можно добавить поле int
, содержащее температуру, и дефолтный конструктор, который принимает int
:
enum Water {
…
final int tempInFahrenheit;
const Water(this.tempInFahrenheit);
}
Чтобы убедиться, что конструктор вызывается при создании enum
, нужно вызывать его для каждого значения enum
:
enum Water {
frozen(32),
lukewarm(100),
boiling(212);
…
}
Чтобы обеспечить преобразование в String
, переопределяем toString
, который enum
наследуют от Object
:
@override
String toString() => "The $name water is $tempInFahrenheit F.";
Таким образом вы получите завершённый enum
, который легко инстанцировать и у которого можно вызвать метод:
void main() {
print(Water.frozen); // Prints “The frozen water is 32 F.”
}
Ниже — подробный пример использования двух этих подходов. Как нам кажется, код в новой версии Dart 2.17 куда проще читать и поддерживать.
![Раньше: приходится использовать extension-функции. Теперь: работаем непосредственно с enum. Функции можно переопределить Раньше: приходится использовать extension-функции. Теперь: работаем непосредственно с enum. Функции можно переопределить](https://habrastorage.org/getpro/habr/upload_files/a7a/318/f74/a7a318f74692628a690d7d6c48afbafb.png)
Комментарий Евгения Сатурова
Авторы оригинальной статьи немного лукавят. В реальности мы просто не использовали enum из языка Dart. А если и использовали, то только в тех местах, где никакого другого выхода не было. Мы использовали enum-like самописные классы, но… конечно, это не то, чего ты ожидаешь от языка, который изо всех сил хочешь считать хорошим.
Новый enum единогласно одержал победу в неформальном голосовании в нашей команде на звание самого значимого изменения языка с момента появления поддержки null-safety.
Если разработчики Dart и дальше будут учитывать популярность issues в трекере при выборе новых фич для реализации, то совсем скоро мы увидим data-классы, множественный return, а, быть может, и отмену обязательной точки с запятой. Хотя, что это я… совсем размечтался.
Инициализаторы суперклассов
Если у вас имеется иерархия наследования классов, чаще всего параметры конструктора передают конструктору суперкласса. Для этого подкласс должен:
Перечислить каждый параметр в собственном конструкторе.
Вызвать суперконструктор с этими параметрами.
В результате рождается бойлерплейт: много повторений, код сложнее читать и ещё муторнее поддерживать.
Несколько членов сообщества Dart помогли это исправить: они предложили ввести новую конструкцию, которая бы означала, что тот или иной параметр уже указан в суперклассе.
Мы решили, что идея отличная, и добавили её в Dart 2.17. Решение особенно хорошо подходит для Flutter-кода с виджетами. Более того, когда мы применили эту фичу во Flutter, то обнаружили, что суммарно код сократился почти на две тысячи строк!
![Раньше: передача параметров вручную. Теперь: используем super.параметры! Раньше: передача параметров вручную. Теперь: используем super.параметры!](https://habrastorage.org/getpro/habr/upload_files/94b/2c2/09a/94b2c209af3021f426a02ff3f0fc1fb4.png)
Пишем именованные аргументы где угодно
Мы переделали механизм работы именованных аргументов при вызове метода. Раньше такие сущности, согласно правилам, оказывались в конце списка аргументов соответствующего метода. Это раздражает, если хочется сделать код читабельнее и разместить в конце позиционный аргумент.
К примеру, ниже показано, как вызывается конструктор List<T>.generate
. Раньше аргумент growable пришлось бы расположить в конце: там его легко потерять после крупного позиционного аргумента, который ещё и сам содержит генератор. Теперь располагать их можно в произвольном порядке: небольшой именованный аргумент можно вывести в начало, а генератор — в конец.
![Раньше: именованные аргументы всегда в конце. Теперь: в удобном вам порядке Раньше: именованные аргументы всегда в конце. Теперь: в удобном вам порядке](https://habrastorage.org/getpro/habr/upload_files/2a4/691/46d/2a469146de2035dc202412fedd46d9e8.png)
Подробнее — в обновлённых примерах для:
Комментарий Евгения Сатурова
Две описанные выше доработки выглядят незначительно, но их важность раскрывается в полной мере в кодовой базе действительно больших и запутанных проектов. Мы с осторожностью относимся к синтаксическому сахару, особенно когда выгода от его использования не очевидна и делится на ноль неоднозначностью, которую он привносит в код.
К счастью, обе доработки – это лаконичность, которая всё ещё помогает коду «рассказывать историю».
Изменения в ключевые инструменты для повышения продуктивности
В Dart 2.14 мы включали package:lints
. Вместе с анализатором Dart он помогал избегать ошибок при написании Dart-кода и следовать каноническому стилю, который облегчал код-ревью.
С тех пор в анализаторе стало доступно несколько новых линтеров: мы тщательно их отсортировали и выбрали десять новых линтеров для любого кода на Dart и два новых линтера, касающихся кода во Flutter. Сюда вошли линтеры, с помощью которых можно убедиться, что импортируемые сущности включены в файл pubspec, и избежать ошибок с проверками на null в параметрах. Также появились линтеры, обеспечивающие консистентность форматирования дочерних свойств. Обновиться до новых линтеров можно с помощью команды:
Для пакетов Dart:
dart pub upgrade —-major-versions lints
Для пакетов Flutter:
flutter pub upgrade —-major-versions flutter_lints
SecureSockets часто используют для работы с TCP-сокетами, защищёнными TLS- и SSL-протоколами. До выхода Dart 2.17 дебажить такие сокеты в процессе разработки было проблематично: отсутствовала возможность проверить защищённый процесс передачи данных.
Однако теперь мы добавили возможность указывать файл keyLog
. В таком случае текстовая строка в формате NSS Key Log добавляется к файлу, как только программа и сервер обмениваются новыми TLS-ключами. Благодаря этому анализаторы сетевого трафика (к примеру, Wireshark) получают возможность дешифровать информацию, отправленную через сокет. Подробнее об этом можно почитать в документации по API для SecureSocket.connect().
Документация к API, сгенерированная инструментом dart doc
, — это крайне ценный ресурс для большинства Dart-разработчиков, изучающих новые API. Несмотря на то, что API наших базовых библиотек уже давно сопровождаются исчерпывающими текстовыми описаниями, многие разработчики поделились, что предпочитают изучать API по примерам кода.
В Dart 2.17 мы полностью переработали основные библиотеки и добавили примеры кода на 200 наиболее популярных страниц. Сравните документацию к dart:convert в Dart 2.16 и обновлённую страницу для Dart 2.17.
Продуктивность растёт не только с появлением новых фич на платформе, но и с уменьшением их числа — если из стека удаляются фичи, которыми давно не пользуются. Таким образом, масштаб новой информации остаётся сравнительно небольшим: это крайне важно для неопытных разработчиков.
Мы удалили 231 строк кода со статусом deprecated из библиотеки dart:io
. Если вы до сих пор пользуетесь этими deprecated API, вы можете обновиться с помощью dart fix и использовать API, пришедшие им на замену. Также мы продолжили устранять deprecated инструменты Dart CLI, и в этот раз удалили dartdoc
(вместо него можно использовать dart doc
) и pub
(можно использовать dart pub
или flutter pub
).
Расширение кроссплатформенной интеграции и поддержки
Вторая ключевая тема этого релиза — кроссплатформенная интеграция и поддержка. Dart — самый настоящий кроссплатформенный язык. Мы уже поддерживаем широкий спектр платформ и непрерывно двигаемся вперёд, чтобы обеспечить возможность глубокой интеграции с каждой из поддерживаемых платформ, а также поддержать молодые развивающиеся платформы.
Dart FFI, наш ключевой механизм взаимодействия с C/нативным кодом, — популярный способ интеграции Dart-кода в уже имеющийся нативный код платформы. Во Flutter это отличная возможность для создания плагинов, которые используют нативные API из платформы устройства (к примеру win32 API на Windows).
В Dart 2.17 и Flutter 3 мы добавили шаблоны к консольной команде flutter
. Теперь вы можете с лёгкостью создавать FFI плагины, в которых Dart API поддерживается обращениями dart:ffi
к нативному коду. Более подробную информацию можно найти на обновлённой странице «Разработка пакетов и плагинов» на flutter.dev.
Мы добавили в FFI поддержку платформ с ABI-специфичными типами. К примеру, теперь можно использовать Long (long
в языке C), чтобы точнее представить длинный целочисленный тип с ABI-специфичным размером, который может составлять 32 бит и 64 бит в зависимости от архитектуры ЦП. С полным списком поддерживаемых типов вы можете ознакомиться в перечне под названием «Implementers» на странице AbiSpecificInteger API.
При глубокой интеграции с нативными платформами через Dart FFI иногда требуется расставить приоритеты при очистке памяти или других ресурсов (портов, файлов и так далее), занятых Dart-кодом или нативным кодом. Эта задача всегда была непростой: Dart управляет сборкой мусора автоматически.
В Dart 2.17 она решается с помощью концепта под названием Finalizer, у которого есть интерфейс-маркер Finalizable
. С его помощью можно «помечать» объекты, которые не следует завершать или удалять преждевременно. Появился класс NativeFinalizer
, который можно прикрепить к объекту Dart и таким образом запустить колбэк, когда объекту будет грозить удаление. Вместе две этих сущности позволяют запускать сборку мусора как в нативном, так и в Dart-коде.
Подробную информацию с описанием и примерами вы можете прочитать в документации к API NativeFinalizer или в документации к WeakReferences и Finalizer, если вас интересует поддержка аналогичных возможностей в простом Dart-коде.
Поддержка компиляции Dart в нативный код — ключевой фактор, благодаря которому у приложений на Flutter отличная производительность при запуске и быстрый рендеринг. Второй фактор — возможность компилировать Dart в исполняемые файлы с помощью dart compile. Такие файлы можно запускать самостоятельно на любой машине без необходимости устанавливать Dart SDK. Ещё одна возможность, которую мы добавили в Dart 2.17, — поддержка подписи исполняемых файлов, благодаря которой можно развернуть файл на Windows или macOS, где зачастую требуется подпись.
Помимо этого мы продолжаем расширять набор поддерживаемых платформ и следим за разработками новых платформ. К примеру, RISC-V — новый инновационный набор инструкций для процессоров. RISC-V International, международная некоммерческая организация, владеет правами на RISC-V спецификации и предоставляет свободный доступ к инструкциям бесплатно. Пока платформа ещё совсем новая, но мы считаем, что у неё есть потенциал: в релиз 2.17.0–266.1.beta
для Linux (и далее в бета-канале) мы включили её экспериментальную поддержку.
Нам очень интересно ваше мнение! Обязательно поделитесь своими впечатлениями в разделе Issues или напишите отзыв здесь.
Dart 2.17 готов к работе
Чтобы начать, скачайте релиз Dart 2.17 отдельно или вместе с релизом Flutter 3 SDK.
И не пропустите новый контент, к которому мы открыли доступ в рамках Google I/O.
Комментарий Евгения Сатурова
Новый релиз Dart 2.17 удался. Все без исключения изменения важные и нужные. Спешите обновляться до Flutter 3, чтобы получить доступ к новым возможностям языка.
Ну а мы возобновляем набор в нашу Flutter-команду. Если вы хотите вместе с нами заниматься развитием опен-сорса, работать над сложными и интересными R&D задачами, а также делать красивые и функциональные приложения, будем рады познакомиться и поработать вместе!
Больше полезного про Flutter — в телеграм-канале Surf Flutter Team. Публикуем кейсы, лучшие практики, новости и вакансии Surf, а также проводим прямые эфиры. Присоединяйтесь!
Больше кейсов команды Surf ищите на сайте >>