Сегодня мы объявляем о стабильном выпуске Dart 2.7 SDK с дополнительными новыми возможностями для разработчиков. Это был напряженный год для Dart, нашего оптимизированного для клиентов языка для быстрых приложений на любой платформе. Мы выпустили шесть новых выпусков с десятками новых функций. Было очень приятно видеть, что сообщество Dart использует эти функции, и мы были рады недавнему отчету GitHub Octoverse, в котором Dart был назван языком №1 наиболее быстрорастущим по количеству участников.

Dart 2.7 добавляет поддержку методов расширения, а также новый пакет для обработки строк со специальными символами. У нас есть обновление по нулевой безопасности (типобезопасные типы, допускающие значение NULL и не допускающие значение NULL) и совершенно новый игровой процесс с нулевой безопасностью в DartPad. На уровне экосистемы pub.dev имеет новую функцию Like, позволяющую оставлять отзывы о пакетах, которые вам нравятся. Dart 2.7 доступен сегодня как SDK для загрузки с сайта dart.dev, а также встроен в сегодняшнюю Flutter 1.12 release.

Методы расширения

Dart 2.7 добавляет долгожданную мощную новую языковую функцию: методы расширения. Это позволяет вам добавлять новые функции к любому типу - даже к типам, которые вы не контролируете - и иметь краткость и возможность автозаполнения обычных вызовов методов.

Давайте посмотрим на небольшой пример: добавляем поддержку синтаксического анализа целых и двойных чисел из строк. Как разработчики приложений, мы не можем изменить класс String, потому что он определен в библиотеке dart: core, но с помощью методов расширения мы можем его расширить! После определения этого расширения мы можем вызвать наш новый метод parseInt для String, как если бы метод был определен в самом классе String:

extension ParseNumbers on String {
  int parseInt() {
    return int.parse(this);
  }
  double parseDouble() {
    return double.parse(this);
  }
}
main() {
  int i = '42'.parseInt();
  print(i);
}

Методы расширения статичны

Методы расширения разрешаются и отправляются статически, что означает, что вы не можете вызывать их для значений типа dynamic. Здесь вызов вызывает исключение во время выполнения:

dynamic d = '2';
d.parseInt();
→ Runtime exception: NoSuchMethodError

Методы расширения хорошо работают с выводом типа Дарта, поэтому в следующей переменной v предполагается, что она имеет тип String, и доступно расширение для String:

var v = '1';
v.parseInt(); // Works!

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

Расширения могут иметь переменные типа

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

extension FancyList<T> on List<T> {
  List<T> get evenElements {
    return <T>[for (int i = 0; i < this.length; i += 2) this[i]];
  }
}

Методы расширения на самом деле являются членами расширения

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

extension ShiftString on String {
  String operator <<(int shift) {
    return this.substring(shift, this.length) + this.substring(0, shift);
  }
}

Отличные примеры из сообщества

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

Иеремия Огбомо создал пакет времени, который использует расширения num (базовый класс для целых и двойных чисел), чтобы упростить создание Duration объектов:

// Create a Duration via a `minutes` extension on num.
Duration tenMinutes = 10.minutes;
// Create a Duration via an `hours` extension on num.
Duration oneHourThirtyMinutes = 1.5.hours;
// Create a DateTime using a `+` operator extension on DateTime.
final DateTime afterTenMinutes = DateTime.now() + 10.minutes;

Марсело Гласберг создал пакет i18n (интернационализация), который использует методы расширения для упрощения локализации строк:

Text('Hello'.i18n) // Displays Hello in English, Hola in Spanish, etc.

Саймон Лейер создал пакет dartx, который содержит расширения для ряда основных типов Dart. Некоторые примеры:

var allButFirstAndLast = list.slice(1, -2);    // [1, 2, 3, 4]
var notBlank = ' .'.isBlank;                   // false
var file = File('some/path/testFile.dart');
print(file.name);                              // testFile.dart
print(file.nameWithoutExtension);              // testFile

Брайан Иган обновляет популярный пакет RxDart методами расширения, чтобы переопределить API для работы с потоками.

Безопасная обработка подстроки

Стандартный класс String в Dart использует кодировку UTF-16. Это распространенный выбор языков программирования, особенно тех, которые предлагают поддержку работы как на устройствах, так и в Интернете.

Строки UTF-16 обычно работают хорошо, а кодировка прозрачна для разработчика. Однако при манипулировании строками, и особенно при манипулировании строками, введенными пользователями, вы можете почувствовать разницу между тем, что пользователь воспринимает как символ, и тем, что закодировано как кодовая единица в UTF-16. Давайте посмотрим на небольшой пример, извлекающий первые три символа строки, введенной пользователем:

var input = ['Resume'];
input.forEach((s) => print(s.substring(0, 3)));
$ dart main.dart
Res

Пока проблем нет; мы напечатали первые три символа строки в нашем списке ввода, и результат - Res. Теперь давайте рассмотрим пользователей из разных регионов, которые могут вводить строки с диакритическими знаками, хангыль (корейский шрифт) и даже комбинацию эмодзи для представления концепции «резюме»:

// New longer input list:
var input = ['Resume', 'Résumé', '이력서', '💼📃', 'Currículo'];
$ dart main.dart
Res
Ré
이력서
💼�
Cur

Хм, некоторые из них работали, но что случилось с элементами Résumé и 💼📃? Почему для Résumé мы получили двухсимвольную строку? Что случилось с 💼📃 со странным вопросительным знаком? Проблемы здесь кроются в темных уголках Unicode. Акцентированный в Résumé на самом деле является двумя кодовыми точками: e и сочетанием острого акцента. И 📃, эмодзи страница с завитком, представляет собой единую кодовую точку, которая закодирована суррогатной парой U+d83d U+dcc3. Смущенный?

Как мы уже говорили, часто вам не нужно беспокоиться о символах и кодовых точках. Если все, что вы делаете, это получать, передавать и передавать целые строки, внутренняя кодировка будет прозрачной. Но если вам нужно перебрать символы строки или манипулировать содержимым строки, у вас могут возникнуть проблемы. Хорошей новостью является то, что Dart 2.7 представляет новый пакет characters для обработки таких случаев. Этот пакет поддерживает строки, рассматриваемые как последовательности воспринимаемых пользователем символов, также известные как кластеры графем Unicode. С помощью пакета символов мы можем исправить наш код, немного изменив код, который укорачивает текст:

// Before:
input.forEach((s) => print(s.substring(0, 3)));
// After, using the characters package:
input.forEach((s) => print(s.characters.take(3)));

Сначала мы создаем новый экземпляр Characters из строки в s (используя удобный метод расширения .characters). Затем мы используем изящный метод take() для извлечения первых трех символов.

Техническая предварительная версия этого нового пакета доступна на pub.dev. Мы будем рады услышать ваше мнение об этом пакете. Если вы обнаружите какие-либо проблемы, сообщите о них.

Предварительный просмотр нулевой безопасности

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

void main() {
  Person('Larry', birthday: DateTime(1973, 03, 26)).describe();
  Person('Sergey').describe();
}
class Person {
  String firstName;
  DateTime birthday;
  Person(this.firstName, {this.birthday});
  void describe() {
    print(firstName);
    int birthyear = birthday?.year;
    print('Born ${DateTime.now().year - birthyear} years ago');
  }
}

Если мы запустим этот код, он выйдет из строя с исключением нулевого указателя при описании второго человека, потому что для этого человека не задан день рождения. Мы допустили ошибку кодирования: хотя мы ожидали, что у некоторых людей дни рождения неизвестны, сделав поле birthday необязательным в конструкторе и проверив нулевой день рождения в birthday?.year, мы забыли обработать случай, когда birthyear имеет значение NULL.

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

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

  1. Чтобы указать, что день рождения может быть нулевым, измените
    DateTime birthday на DateTime? birthday
  2. Чтобы объявить, что год рождения может иметь значение NULL, если значение дня рождения равно NULL, измените
    int birthyear на int? birthyear
  3. Оберните последний вызов печати в нулевой тест:
    if (birthyear != null) {…}

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

  1. Завершение полной реализации ссылок, допускающих и не допускающих значения NULL
  2. Интеграция нулевой безопасности в вывод типов и интеллектуальное продвижение Dart (например, обеспечение безопасного доступа к переменной, допускающей значение NULL, после присваивания или проверки на нуль)
  3. Перенос основных библиотек Dart, чтобы объявить, какие типы допускают значение NULL, а какие не допускают значения NULL.
  4. Добавление инструмента миграции, который может автоматизировать большинство задач обновления для переноса приложений и пакетов Dart.

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

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

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

Пакеты "Нравится" на pub.dev

Сегодня на pub.dev также запускается новая функция Мне нравится для пакетов. Это вводит новый «человеческий сигнал», указывающий, какие пакеты вам нравятся. Чтобы поставить лайк пакету, просто нажмите значок с изображением большого пальца рядом с подробной информацией о пакете:

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

Спасибо

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

2019 год был невероятно захватывающим для Dart, но мы не останавливаемся на достигнутом. У нас смелые планы на 2020 год, включая выпуск стабильных версий таких функций, как dart: ffi и null security, а также внедрение новых функций. Мы приглашаем вас начать использовать Dart 2.7 уже сегодня. Он доступен на dart.dev, в сегодняшнем выпуске Flutter 1.12 и на недавно переработанном DartPad.