Pull to refresh

Перечень изменений в D 2.092. Заимствование заимствования

Reading time12 min
Views1.6K
Original author: Martin Nowak
От переводчика. Обычно не перевожу подобные статьи, т.к не вижу в них ничего интересного. Здесь же, два небольших, но важнейших нововведения — поддержка прямой линковки C++ библиотек и «позаимствованная» из Rust система владения и заимствования (вторая ласточка DFA, первая вышла в GCC10 буквально пару дней как, и в Nim тоже задумываются о подобном) с описанием. Собственно, всякие нудные перечни я не переводил, а ссылки ведут на оригинал.

Предыдущая версия перечня 2.0.91.1 (англ)
Где загрузить

2.092.0 вышла с 15 важными изменениями и 44 исправленными ошибками и улучшениями. Огромная благодарность 47 контрибьюторам, которые сделали этот релиз возможным.

Изменения компилятора


  1. Ключи командной строки -revert=import и -transition=checkimports удалены
  2. Добавлена поддержка кодирования (манглинга) имен C++ GNU ABI
  3. Конструкторы и деструкторы для модулей, которые не extern(D) помечаются устаревшими
  4. Нарушения DIP25 по умолчанию помечаются устаревшими
  5. Прототип системы владения и заимствования для указателей
  6. Добавлен ключ -preview=in превращающий класс хранения in в scope const
  7. Параметры printf и scanf теперь проверяются на соответствие спецификаторам формата
  8. Теперь поддерживается переменная окружения SOURCE_DATE_EPOCH

Изменения рантайма


  1. Добавлены TypeInfo_Class/TypeInfo_Interface.isBaseOf аналогичные C#/Java isAssignableFrom
  2. Добавлены core.memory.pageSize и minimumPageSize

Изменения Библиотеки


  1. Добавлены Date.isoWeekYear и Date.fromISOWeek в std.datetime.date
  2. Модуль std.xml помечается устаревшим
  3. Устаревшие синонимы в std.digest.digest удалены

Изменения системы сборки Dub


  1. Скрытые каталоги теперь игнорируются
  2. Dub lint теперь поддерживает параметр --report-file

Полный перечень исправлений и улучшений (англ)

Прим.пер.У меня лапки, потому не получается из оглавления настроить корректные внутристраничные ссылки на раскрытие тем, как в оригинале, извиняйте =(

Изменения компилятора


  1. Ключи командной строки -revert=import и -transition=checkimports удалены.

    Эти ключи уже ничего не делали, и какое-то время они были помечены устаревшими. Компилятор их больше не распознает.
  2. Добавлена поддержка кодирования (манглинга) имен C++ GNU ABI

    Теги GNU ABI это функционал C++11 появившийся в GCC 5.1. Для обеспечения полной поддержки D вызовов Стандартной Библиотеки C++, DMD теперь распознает специальный UDA (пользовательский атрибут) gnuAbiTag, определенный в core.attribute и имеющий публичный синоним в object (так что никому не нужно ничего импортировать для его использования). ABI теги это низкоуровневая функциональность, которая для большинства пользователей прозрачна, но может использоваться для связывания специфических С++ библиотек. В частности, она нужна для использования std::string когда связывание идет с C++11 или выше (DMD ключ -extern-std={c++11,c++14,c++17}).

    Используется следующим образом:

    extern(C++):
    @gnuAbiTag("tagOnStruct")
    struct MyStruct {}
    @gnuAbiTag("Multiple", "Tags", "On", "Function")
    MyStruct func();

    Для внешнего имени может назначаться единственный gnuAbiTag. Порядок в массиве неважен (сортируется при выводе). UDA будет действовать только если компилятору указан ключ -extern-std=c++11. По умолчанию (-extern-std=c++98) UDA игнорируются. UDA применяются только для extern(C++) внешних имен и не применимы к пространствам имен.
  3. Конструкторы и деструкторы для модулей, которые не extern(D) помечаются устаревшими.

    Конструкторы и деструкторы модулей ( разделяемые или же нет) могут быть помечены другой линковкой, отличной от extern(D), что повлияет на их манглинг. Так как искажение имен просто и предсказуемо, вероятность конфликта очень мала, если бы два одинаковых конструктора/деструктора были объявлены в схожих условиях, например, если бы третий конструктор модуля в модуле А был на строке 479, а третий конструктор модуля В также был на строке 479, то они имели бы одинаковые манглинги.

    Хотя маловероятно, что такая ошибка встретится на практике, но затронутые имена теперь будут приводить к сообщению об устаревании.
  4. Нарушения DIP25 по умолчанию помечаются устаревшими.

    DIP25 доступен с версии 2.067.0, сначала как собственный ключ, а с недавнего времени под ключом -preview=dip25. Теперь эта функция полностью работоспособна и была построена на основе, например, DIP1000.

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

    DIP25 направлен на то, чтобы @ safe код не мог ссылаться на разрушенный объект. На практике функции и методы, возвращающие ссылку ref на свой параметр, могут потребовать return для квалификации метода или параметра, как подскажет компилятор.

    struct Foo
    {
        int x;
        // returning `this.x` escapes a reference to parameter `this`, perhaps annotate with `return`
        ref int method() /* return */ { return this.x; }
    }
    // returning `v` escapes a reference to parameter `v`, perhaps annotate with `return`
    ref int identity(/* return */ ref int v) { return v; }

    В обоих случаях раскомментирование return успокоит компилятор. Полное описание DIP25 можно найти здесь.
  5. Добавлен ключ -preview=in превращающий класс хранения in в scope const.

    Хотя технически и бывший определенным как const scope, класс хранения in практически не был реализован до появления этого ключа. Теперь все сделано, in должен стать предпочтительным классом хранения для входных (т.е не inout итп) параметров функций.

    Без ключа -preview=in, эти два определения эквивалентны:

    void fun(in int x);
    void fun(const int x);

    С ключом -preview=in, другие два определения эквивалентны:

    void fun(in int x);
    void fun(scope const int x);
  6. Параметры printf и scanf теперь проверяются на соответствие спецификаторам формата

    Соответствует спецификации C99 7.19.6.1 для printf и 7.19.6.2 для scanf.

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

    Для scanf применяется строгий подход к совместимости.

    Диагностируются следующие несовместимости:

    1. несовместимые размеры, что приведет к неправильному выравниванию аргументов
    2. разыменование аргументов, которые не являются указателями
    3. недостаточное количество аргументов
    4. структурные аргументы
    5. аргументы массива и слайса
    6. аргументы — не указатели, но со спецификатором s
    7. нестандартные форматы
    8. неопределённое поведение в соответствии с C99

    Согласно С-стандарту, дополнительные аргументы игнорируются.

    Никаких попыток исправить аргументы или строку форматирования не предпринимается.

    Для того, чтобы использовать нестандартные форматы printf/scanf, есть простой обходной путь:

    printf("%k\n", value);  // error: non-Standard format k
    const format = "%k\n";
    printf(format.ptr, value);  // no error

    Большинство обнаруживаемых ошибок — это проблемы с переносимостью. Например,

    string s;
    printf("%.*s\n", s.length, s.ptr);
    printf("%d\n", s.sizeof);
    ulong u;
    scanf("%lld%*c\n", &u);

    следует заменить на:

    string s;
    printf("%.*s\n", cast(int) s.length, s.ptr);
    printf("%zd\n", s.sizeof);
    ulong u;
    scanf("%llu%*c\n", &u);

    Функции подобные printf и scanf определяются путем их префиксации с помощью pragma(printf) для функций типа printf или pragma(scanf) для функций типа scanf.

    Помимо прагмы, функции должны соответствовать следующим характеристикам:

    1. быть extern (C ) или extern (C++)
    2. обладать строкой формата определенную как const(char)*
    3. строка формата должна непосредственно предшествовать… для не-v функций, или же непосредственно предшествовать va_list параметру (который идет последним для «v» вариантов printf и scanf)
    что позволяет автоматически определить аргумент строки формата и список аргументов.

    Проверка формата для «v»-функций пока не реализована.
  7. Теперь поддерживается переменная окружения SOURCE_DATE_EPOCH

    Для воспроизводимых сборок используется переменная окружения SOURCE_DATE_EPOCH. Это временная метка UNIX (секунды с 1970-01-01 00:00:00), как описано здесь. Теперь DMD правильно распознает ее и устанавливает маркеры __DATE__, __TIME__ и __TIMESTAMP__ соответственно.


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


Система владения/заимствования (так же известная как OB) для указателей гарантирует, что разыменованные указатели указывают на допустимый объект памяти.

Область применения прототипа OB системы


Это прототип OB системы, адаптированный для D. Изначально он предназначен только для указателей, а не для динамических массивов, ссылок на классы, ссылок или полей указателей в агрегатах. Добавление поддержки такого рода усложнит реализацию, но не меняет ее сути, следовательно, откладывается на потом. RAII-объекты могут безопасно управлять собственной памятью, поэтому не покрываются OB. Независимо от способа, выделяет ли указатель память с помощью GC или какого-то другого аллокатора, это несущественно для OB, они не различаются и обрабатываются идентично.

Система ОВ активна только в функциях, аннотированных атрибутом @ live. Применяется после семантической обработки исключительно как проверка на нарушение правил ОВ. Новый синтаксис не добавляется. В сгенерированный код не вносится никаких изменений. Если функции @ live, вызывают функции, не являющиеся @ live, ожидается, что эти вызываемые функции будут представлять собой @ live-совместимый интерфейс, хотя он не проверяется. Если функции, не являющиеся @ live, вызывают функции @ live, ожидается, что передаваемые аргументы будут следовать конвенциям @ live.

Система OB обнаруживает ошибки:

  • разыменование указателей, которые находятся в недействительном состоянии
  • более одного активного указателя на объект мутируемой памяти.

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

Основной принцип OB


Дизайн ОВ следует из следующего принципа:

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

Дизайн


Одиночный мутирующий указатель называется «владельцем» объекта памяти. Ему транзитивно принадлежит объект памяти и все объекты в памяти, доступные из него (т.е. граф объектов). Так как он является единственным указателем на этот объект памяти, он может безопасно управлять памятью (изменять её форму, выделять, освобождать и изменять размер), не выбивая почву из под ног любых других указателей (мутирующих или нет), которые могут на него (граф памяти) указывать.

Если есть несколько указателей на граф объектов памяти, доступных только для чтения, то они могут безопасно читать из него, не беспокоясь о внезапных изменениях графа объектов в памяти.

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

Отслеживаемые указатели
Отслеживаются только те указатели, которые объявлены в функции @ live как this, параметры функции либо локальные переменные. Переменные из других функций не отслеживаются, даже @ live, так как анализ взаимодействий с другими функциями полностью зависит только от сигнатуры этой функции, но не от ее внутренностей. Параметры, которые являются const, не отслеживаются.

Состояния указателя
Каждый указатель находится в одном из следующих состояний:

Неопределённый
Указатель находится в недействительном состоянии. Разыменование такого указателя является ошибкой.
Владелец
Владелец является единственным указателем на граф объектов в памяти. Обычно указатель — Владелец не имеет атрибута scope. Если указатель с атрибутом scope инициализируется выражением, не производным от отслеживаемого указателя, то он становится Владельцем.
Если указатель Владелец присваивается другому указателю — Владельцу, то первый переходит в состояние Неопределённого.
Заимствованный
Заимствованный указатель — это указатель, который временно становится единственным указателем на граф объектов памяти. Он переходит в это состояние через присвоение от указателя — Владельца, при этом Владелец переходит в состояние Заемщика до завершения использования заимствованного указателя.
Заимствованный указатель должен иметь атрибут scope и быть указателем на мутируемый объект.
Только для чтения
Указатель Только для чтения присваивается от Владельца. Пока указатель Только для чтения жив, только указатели Только для чтения могут быть еще присвоены от этого Владельца. Указатель Только для чтения должен иметь атрибут scope, а также не должен быть указателем на мутируемый объект.

Время жизни (Lifetimes)

Время жизни указателя «Заимствованный» или «Только для чтения» начинается с момента его первого разыменования (а не с момента инициализации или присвоения значения) и заканчивается при последнем разыменовани этого значения.

Это также известно как Non-Lexical Lifetimes.

Переходы состояний для указателей

Указатель меняет свое состояние при выполнении одной из этих операций:

  • для него выделяется память (например, локальная переменная на стеке), которая помещает указатель в неопределенное состояние
  • инициализация (рассматривается как присваивание)
  • присваивание — исходный и целевой указатели изменяют состояние в зависимости от того, в каких состояниях они находятся, а также от их типов и классов хранения
  • передается параметру функции out (изменяет состояние после возвращения функции), обрабатывается так же, как и инициализация.
  • передается как ref параметр функции, что рассматривается как назначение для заимствования или только для чтения, в зависимости от класса хранения и типа параметра.
  • возвращается функцией
  • передается по значению параметру функции, что рассматривается как присвоение этому параметру.
  • неявно передается по ссылке как переменная в функцию-замыкание (лямбда)
  • берется адрес указателя, что рассматривается как присваивание тому, кто получит адрес.
  • берется адрес любой части графа объектов памяти, что рассматривается как присваивание тому, кто получит этот адрес
  • значение указателя считывается из любой части графа объектов памяти, что рассматривается как присваивание тому, кто получит этот указатель
  • слияние потоков управления согласовывает состояние каждой переменной на основе состояний, которые они имеют с обеих сторон

Ограничения


Будучи прототипом, есть много аспектов, которые еще не рассмотрены, и не будут до тех пор, пока прототип не покажет, что это работоспособное решение.

Ошибки

Ожидайте множества ошибок. Пожалуйста, сообщите о них в bugzilla и пометьте ключевым словом «ob». Нет необходимости сообщать о других ограничениях, которые здесь уже перечислены.

Ссылки на классы и ассоциативные массивы не отслеживаются.
Предполагается, что ими управляет GC.

Заимствование и чтение у не-Владельцев

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

@live void uhoh()
{
    scope p = malloc();  // p is considered an Owner
    scope const pc = malloc(); // pc is not considered an Owner
} // dangling pointer pc is not detected on exit

Казалось бы, не имеет смысла помечать такие указатели, как scope, возможно, это можно исправить, рассматривая подобное как ошибку.

Указатели читаются/пишутся вложенными функциями
Не отслеживаются.

@live void ohno()
{
    auto p = malloc();

    void sneaky() { free(p); }

    sneaky();
    free(p);  // double free not detected
}

Исключения

Анализ предполагает, что исключения не выбрасываются.

@live void leaky()
{
    auto p = malloc();
    pitcher();  // throws exception, p leaks
    free(p);
}

Одним из решений является использование scope(exit):

@live void waterTight()
{
    auto p = malloc();
    scope(exit) free(p);
    pitcher();
}

или используйте RAII-объекты или вызывайте только функции nothrow.

Lazy параметры

Не принимаются во внимание.

Квадратичная сложность

Анализ демонстрирует квадратичную сложность, старайтесь сохранять функции @ live небольшими.

Смешивание разных групп памяти

Объединение различных пулов памяти:

void* xmalloc(size_t);
void xfree(void*);

void* ymalloc(size_t);
void yfree(void*);

auto p = xmalloc(20);
yfree(p);  // should call xfree() instead

не обнаруживается.

Это можно обойти, используя пулы, специфичные для конкретного типа:

U* umalloc();
void ufree(U*);

V* vmalloc();
void vfree(V*);

auto p = umalloc();
vfree(p);  // type mismatch

и, возможно, запретить неявные преобразования в void* в функциях @ live.

Аргументы вариадической функции

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

Изменения рантайма


  1. Добавлены TypeInfo_Class/TypeInfo_Interface.isBaseOf аналогичные C#/Java isAssignableFrom.

    TypeInfo_Class.isBaseOf возвращает true, если аргумент и приемник равны или если класс, представленный аргументом, наследует от класса, представленного приемником. Это называется isBaseOf, а не isAssignableFrom, чтобы избежать путаницы для классов, которые перегружают opAssign и поэтому могут позволить присваивать классы вне иерархии наследования и соответствовать существующей терминологии для D — рантайма. TypeInfo_Interface.isBaseOf аналогичен с добавлением того, что аргумент может быть либо TypeInfo_Class, либо TypeInfo_Interface.
  2. Добавлены core.memory.pageSize и minimumPageSize.

    pageSize содержит размер системной страницы в байтах.

    import core.memory : pageSize;
    ubyte[] buffer = new ubyte[pageSize];

    minimumPageSize содержит минимальный размер системной страницы в байтах.

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

    Минимальный размер полезен, когда контекст требует известного значения на момент компиляции, например, размер буфера статического массива: ubyte[minimumPageSize].

    import core.memory : minimumPageSize;
    ubyte[minimumPageSize] buffer;


Изменения Библиотеки


  1. Добавлены Date.isoWeekYear и Date.fromISOWeek в std.datetime.date

    Теперь можно конвертировать из недельного календаря ISO 8601 в григорианский календарь, который используется в Date and DateTime.

    Даты в недельном календаре ISO строятся из года, номера недели и дня недели. Date.fromISOWeek(2020, 11, DayOfWeek.mon) приведет к Date(2020, 3, 9).

    Поскольку год в григорианском календаре и год в недельном календаре ISO не всегда совпадают, существует новое свойство (проперти) .isoWeekYear для получения года объекта Date в недельном календаре ISO. Если вы часто конвертируете между ними, подумайте об использовании .isoWeekAndYear для вычисления как номера недели, так и года за один шаг.
  2. Модуль std.xml помечается устаревшим.

    Модуль std.xml стал устаревшим. Любой код, который все еще зависит от него, может использовать вместо UndeaD. Для разбора xml файлов мы рекомендуем использовать dub-пакет dxml.
  3. Устаревшие синонимы в std.digest.digest удалены.

    Они были устаревшими с 2.076.1 и в настоящее время удалены. Вместо этого импортируйте std.digest или его субмодули.

    Дополнительно был запущен цикл устаревания std.digest.digest и модуль будет удален в версии 2.101.

Изменения системы сборки Dub


  1. Скрытые каталоги теперь игнорируются.

    Скрытый каталог на большинстве файловых систем Posix начинается с точки, например .dub. По умолчанию dub игнорирует скрытые файлы (например, .swap.file.d), но не скрытые каталоги. Некоторые операционные системы создают скрытые каталоги, которые dub пытается компилировать. Этот релиз теперь будет корректно игнорировать скрытые каталоги, если только эти каталоги не будут специально названы в файле макета сборки dub.

    Обратите внимание, что при этом используется только имя каталога и не проверяются атрибуты файла.
  2. Dub lint теперь поддерживает параметр --report-file.

    Dub lint теперь можно вызвать с параметром --report-file: dub lint --report-file report.json
Tags:
Hubs:
Total votes 8: ↑8 and ↓0+8
Comments4

Articles