Новая функция языка псевдонимов типов, улучшенный Dart FFI

Авторы: Кевин Мур и Майкл Томсен

Сегодня мы представляем Dart 2.13 с псевдонимами типов - в настоящее время нашей второй наиболее востребованной языковой функцией. Dart 2.13 также включает улучшенный Dart FFI и лучшую производительность, и у нас есть новые официальные образы Docker для Dart. В этом сообщении содержится обновленная информация о функции нулевой безопасности, представленной в 2.12, обсуждаются новые функции 2.13, есть некоторые интересные новости о поддержке Docker и Google Cloud для бэкэндов Dart, а также рассматриваются некоторые изменения, которые вы можете ожидать в будущих выпусках.

Нулевое обновление безопасности

Мы запустили нулевую безопасность в марте в релизе Dart 2.12. Нулевая безопасность - это последняя основная функция Dart для повышения производительности, призванная помочь вам избежать нулевых ошибок - класса ошибок, которые часто трудно обнаружить. Этим запуском мы призвали издателей пакетов начать перенос общих пакетов на pub.dev на нулевую безопасность.

Нам было очень приятно видеть, как быстро была принята нулевая безопасность! Всего через несколько месяцев после запуска 93% из 500 самых популярных пакетов на pub.dev уже поддерживают нулевую безопасность. Мы хотели бы выразить нашу искреннюю благодарность всем разработчикам пакетов за то, что они сделали эту работу так быстро и за то, что помогли всей экосистеме двигаться вперед!

С таким количеством пакетов, поддерживающих нулевую безопасность, есть большая вероятность, что вы сможете начать миграцию своих приложений для использования нулевой безопасности. Первый шаг - использовать dart pub outdated для проверки зависимостей вашего приложения. Подробности см. В Руководстве по нулевой безопасной миграции. Мы также изменили наши dart create и flutter create шаблоны, так что теперь они по умолчанию включают нулевую безопасность в новых приложениях и пакетах.

Объявление псевдонимов типов

Псевдонимы типов - это новая функция в языке 2.13. Он расширяет нашу предыдущую поддержку, которая позволяла создавать псевдонимы типов для функциональных типов, но не для любых других типов. Эта востребованная функция заняла второе место по рейтингу в системе отслеживания языковых проблем.

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

typedef Integer = int;
void main() {
  print(int == Integer); // true
}

Итак, для чего вы можете использовать псевдонимы типов? Одним из распространенных способов использования является присвоение типу более короткого или более информативного имени, что делает ваш код более читабельным и поддерживаемым.

Хороший пример - работа с JSON (спасибо пользователю GitHub Levi-Lesches за этот пример). Здесь мы можем определить новый псевдоним типа Json, который описывает документ JSON как карту от String ключей до любого значения (с использованием типа dynamic). Затем мы можем использовать этот псевдоним типа Json при определении нашего fromJson именованного конструктора и json геттера.

typedef Json = Map<String, dynamic>;
class User {
  final String name;
  final int age;
  User.fromJson(Json json) :
    name = json['name'],
    age = json['age'];
Json get json => {
    'name': name,
    'age': age,
  };
}

Вы также можете вызывать конструкторы для псевдонима типа, который называет класс, поэтому следующее будет совершенно законным:

main() {
  var j = Json();
  j['name'] = 'Michael';
}

Используя псевдонимы типов для присвоения имен сложным типам, вы можете облегчить читателям понимание инвариантов вашего кода. Например, следующий код определяет псевдоним типа для описания карт, содержащих ключи общего типа X и значения типа List<X>. Если дать типу имя с одним параметром типа, обычная структура карты становится более очевидной для читателя кода.

typedef MapToList<X> = Map<X, List<X>>;
void main() {
  MapToList<int> m = {};
  m[7] = [7]; // OK
  m[8] = [2, 2, 2]; // OK
  for (var x in m.keys) {
    print('$x --> ${m[x]}');
  }
}
=>
7 --> [7]
8 --> [2, 2, 2]

Если вы попытаетесь использовать несовпадающие типы, вы получите ошибку анализа:

m[42] = ['The', 'meaning', 'of', 'life'];
=>
The element type 'String' can't be assigned to the list type 'int'.

Вы даже можете использовать псевдонимы типов при переименовании классов в публичных библиотеках. Представьте, что у вас есть класс PoorlyNamedClass в публичной библиотеке, который вы хотите переименовать в BetterNamedClass. Если вы просто переименуете класс, ваши клиенты API получат внезапные ошибки компиляции. С псевдонимами типов вы можете продолжить и выполнить переименование, но затем определить новый псевдоним типа для старого имени класса, а затем добавить аннотацию @Deprecated для старого имени. Использование PoorlyNamedClass затем вызовет предупреждение при использовании, но продолжит компилировать и работать, как раньше, давая пользователям время для обновления своего кода.

Вот как можно реализовать BetterNamedClass и исключить PoorlyNamedClass (в файле с именем mylibrary.dart):

class BetterNamedClass {...}
@Deprecated('Use BetterNamedClass instead')
typedef PoorlyNamedClass = BetterNamedClass;

И вот что происходит, когда кто-то пытается использовать PoorlyNamedClass:

import 'mylibrary.dart';
void main() {
  PoorlyNamedClass p;
}
=>
'PoorlyNamedClass' is deprecated and shouldn't be used. Use BetterNamedClass instead.

Функция псевдонима типа доступна начиная с Dart 2.13. Чтобы включить его, установите нижнее ограничение Dart SDK в своей pubspec как минимум на 2,13:

environment:
  sdk: ">=2.13.0 <3.0.0"

Эта функция обратно совместима благодаря языковым версиям. Пакеты с более низкими ограничениями SDK в версии 2.13 могут безопасно ссылаться на псевдонимы типов, определенные в пакетах 2.13, даже если пакеты до версии 2.13 не могут определять свои собственные псевдонимы типов.

Изменения Dart 2.13 FFI

У нас также есть несколько новых функций в Dart FFI, нашем механизме взаимодействия для вызова кода C.

Во-первых, FFI теперь поддерживает структуры со встроенными массивами (# 35763). Рассмотрим структуру C со встроенным массивом, подобным этому:

struct MyStruct {
  uint8_t arr[8];
}

Теперь вы можете напрямую обернуть это в Dart, указав тип элемента с аргументом типа Array:

class StructInlineArray extends Struct {
  @Array(8)
  external Array<Uint8> arr;
}

Во-вторых, FFI теперь поддерживает упакованные структуры (# 38158). Обычно структуры размещаются в памяти таким образом, чтобы элементы попадали в границы адресов, к которым проще получить доступ для ЦП. В упакованных структурах некоторые из этих заполнений опускаются, чтобы снизить общее потребление памяти, часто в зависимости от платформы. С помощью новой аннотации @Packed(<alignment>) вы можете легко указать отступ. Например, следующий код создает структуру с 4-байтовым выравниванием, когда она находится в памяти:

@Packed(4)
class TASKDIALOGCONFIG extends Struct {
  @Uint32()
  external int cbSize;
  @IntPtr()
  external int hwndParent;
  @IntPtr()
  external int hInstance;
  @Uint32()
  external int dwFlags;
  ...
}

Изменения производительности Dart 2.13

Мы продолжаем работать над уменьшением размера приложения и объема памяти, занимаемой кодом Dart. В больших приложениях Flutter внутренние структуры, представляющие метаданные AOT-скомпилированной программы Dart, могут занимать значительный кусок памяти. Большая часть этих метаданных присутствует для включения таких функций, как горячая перезагрузка, интерактивная отладка и форматирование удобочитаемых трассировок стека - функций, которые никогда не используются в развернутых приложениях. В прошлом году мы реструктурировали встроенную среду выполнения Dart, чтобы максимально устранить эти накладные расходы. Некоторые из этих улучшений применимы ко всем приложениям Flutter, созданным в режиме выпуска, но некоторые требуют, чтобы вы отказались от удобочитаемых трассировок стека, разделив отладочную информацию из приложений, скомпилированных AOT, с помощью флага --split-debug-info.

Dart 2.13 включает ряд изменений, которые значительно сокращают пространство, занимаемое программными метаданными при использовании --split-debug-info. Возьмем, к примеру, приложение Flutter Gallery. На Android выпуск APK составляет 112,4 МБ с отладочной информацией и 106,7 МБ без нее (общее сокращение на 5%). Этот APK содержит много ресурсов. Если посмотреть только на метаданные кода внутри APK, то он был уменьшен с 5,7 МБ в Dart 2.12 и всего с 3,7 МБ в Dart 2.13 (сокращение на 35%).

Если для вас важны размер приложения и объем памяти, попробуйте опустить отладочную информацию с помощью флага --split-debug-info. Обратите внимание, что при этом вам нужно будет использовать команду symbolize, чтобы трассировки стека снова стали удобочитаемыми.

Официальная поддержка Docker и Dart в Google Cloud

Dart теперь доступен как Официальные образы Docker. Хотя Dart предоставляет образы Docker в течение многих лет, эти новые образы Dart тестируются и проверяются Docker на предмет соответствия лучшим практикам. Они также поддерживают предварительную компиляцию (AOT), которая может значительно уменьшить размер встроенных контейнеров и повысить скорость развертывания в контейнерных средах, таких как Cloud Run.

Хотя Dart по-прежнему сосредоточен на том, чтобы такие фреймворки приложений, как Flutter, отображали красивые пиксели на каждом экране, мы понимаем, что за большинством пользовательских интерфейсов стоит по крайней мере одна размещенная служба. Упрощая создание серверных сервисов с помощью Dart, мы поддерживаем полный стек, который позволяет разработчикам расширять свои приложения до облака, используя тот же язык и бизнес-логику, которые они используют для поддержки виджетов во внешнем интерфейсе.

В целом, использование Dart для серверных частей приложений Flutter особенно хорошо подходит для простоты и масштабируемости управляемой бессерверной платформы Google Cloud Run. Это включает масштабирование до нуля, что означает, что вы не несете никаких затрат, если серверная часть не обрабатывает какие-либо запросы. Мы работаем с командой Google Cloud, чтобы предоставить Functions Framework for Dart, набор пакетов, инструментов и примеров, которые упрощают написание функций Dart для развертывания вместо полных серверов для обработки HTTP-запросов и CloudEvents.

Ознакомьтесь с нашей документацией по Google Cloud, чтобы начать работу.

Несколько слов о том, что будет дальше

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

Одна из областей, над которой мы работаем, - это новый набор канонических линтов для Dart и Flutter. Линты - это мощный способ настроить статический анализ Dart, но с сотнями возможных линтов, которые можно включить или выключить, может быть сложно решить, что выбрать. В настоящее время мы работаем над определением двух канонических наборов линтов, которые мы будем применять по умолчанию в проектах Dart и Flutter. Мы ожидаем, что это будет включено по умолчанию в следующем стабильном выпуске. Если вам нужен предварительный просмотр, обратите внимание на два пакета lints и flutter_lints.

Наконец, если вы выполняете глубокое встраивание среды выполнения Dart VM, обратите внимание, что мы планируем отказаться от существующего механизма для этого. Мы заменим ее более быстрой и гибкой моделью на основе Dart FFI (см. Проблему отслеживания № 45451).

Dart 2.13 уже доступен

Dart 2.13 с псевдонимами типов и улучшенным FFI доступен сегодня в SDK Dart 2.13 и Flutter 2.2.

Если вы ждали, пока ваши зависимости перейдут на нулевую безопасность, вы можете проверить еще раз, используя dart pub outdated. Поскольку 93% из 500 самых популярных пакетов уже перенесены, есть большая вероятность, что вы разблокированы. Мы также хотели бы выразить большую благодарность разработчикам, которые уже выполнили миграцию!

Мы будем рады узнать о вашем опыте использования новых функций и изменений, обсуждаемых в этом сообщении в блоге. Оставьте комментарий ниже или напишите нам в Твиттере @dart_lang.