Я — Евгений Сатуров, 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. Функции можно переопределить

Комментарий Евгения Сатурова

Авторы оригинальной статьи немного лукавят. В реальности мы просто не использовали enum из языка Dart. А если и использовали, то только в тех местах, где никакого другого выхода не было. Мы использовали enum-like самописные классы, но… конечно, это не то, чего ты ожидаешь от языка, который изо всех сил хочешь считать хорошим.

Новый enum единогласно одержал победу в неформальном голосовании в нашей команде на звание самого значимого изменения языка с момента появления поддержки null-safety.

Если разработчики Dart и дальше будут учитывать популярность issues в трекере при выборе новых фич для реализации, то совсем скоро мы увидим data-классы, множественный return, а, быть может, и отмену обязательной точки с запятой. Хотя, что это я… совсем размечтался.

Инициализаторы суперклассов

Если у вас имеется иерархия наследования классов, чаще всего параметры конструктора передают конструктору суперкласса. Для этого подкласс должен:

  1. Перечислить каждый параметр в собственном конструкторе.

  2. Вызвать суперконструктор с этими параметрами. 

В результате рождается бойлерплейт: много повторений, код сложнее читать и ещё муторнее поддерживать.

Несколько членов сообщества Dart помогли это исправить: они предложили ввести новую конструкцию, которая бы означала, что тот или иной параметр уже указан в суперклассе. 

Мы решили, что идея отличная, и добавили её в Dart 2.17. Решение особенно хорошо подходит для Flutter-кода с виджетами. Более того, когда мы применили эту фичу во Flutter, то обнаружили, что суммарно код сократился почти на две тысячи строк!

Раньше: передача параметров вручную. Теперь: используем super.параметры!
Раньше: передача параметров вручную. Теперь: используем super.параметры!

Пишем именованные аргументы где угодно

Мы переделали механизм работы именованных аргументов при вызове метода. Раньше такие сущности, согласно правилам, оказывались в конце списка аргументов соответствующего метода. Это раздражает, если хочется сделать код читабельнее и разместить в конце позиционный аргумент. 

К примеру, ниже показано, как вызывается конструктор List<T>.generate. Раньше аргумент growable пришлось бы расположить в конце: там его легко потерять после крупного позиционного аргумента, который ещё и сам содержит генератор. Теперь располагать их можно в произвольном порядке: небольшой именованный аргумент можно вывести в начало, а генератор — в конец.

Раньше: именованные аргументы всегда в конце. Теперь: в удобном вам порядке
Раньше: именованные аргументы всегда в конце. Теперь: в удобном вам порядке

Подробнее — в обновлённых примерах для: 

Комментарий Евгения Сатурова

Две описанные выше доработки выглядят незначительно, но их важность раскрывается в полной мере в кодовой базе действительно больших и запутанных проектов. Мы с осторожностью относимся к синтаксическому сахару, особенно когда выгода от его использования не очевидна и делится на ноль неоднозначностью, которую он привносит в код. 

К счастью, обе доработки – это лаконичность, которая всё ещё помогает коду «рассказывать историю».

Изменения в ключевые инструменты для повышения продуктивности

В 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 >>

Комментарии (4)


  1. lamerok
    26.05.2022 17:40

    Есть ли в DART возможность динамически выполнять код самого DART, скажем аналог Eval в JavaSctipt?


    1. danial72
      26.05.2022 18:26

      Нет и не будет



  1. Mitai
    27.05.2022 16:43

    киньте ссылочкой про дата классы, не могу найти