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

Типичными проблемами являются:

  1. утечки памяти (не освобождение более не используемой памяти)
  2. двойное освобождение (высвобождение памяти более одного раза)
  3. использование после освобождения (использование указателя на память, ранее уже освобождённую)

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

Типовыми решениями являются следующие:

  1. Сборщик мусора (Garbage Collection, GC) – GC владеет блоками памяти и периодически их сканирует на предмет наличия указателей на эти блоки. Если указатели не найдены, память высвобождается. Эта схема надежна и применяется в таких языках как Go и Java. Но GC имеет склонность к использованию гораздо большего количества памяти, чем необходимо, имеет паузы и замедляет код из-за переупаковки (orig.inserted write gates).
  2. Подсчет ссылок (Reference Counting, RC) – RC объект владеет памятью и хранит счетчик указателей на себя. Когда этот счетчик уменьшается до нуля, память высвобождается. Это также надежный механизм и принят в языках типа C++ и ObjectiveC. RC эффективен по памяти, требуя дополнительно только место под счетчик. Негативными сторонами RC являются накладные расходы на поддержание счетчика, встраивание обработчика исключений для гарантированного его уменьшения, и блокировки, необходимые для разделяемых между потоками программы объектов. Для повышения производительности, программисты иногда хитрили, временно ссылаясь на RC объект в обход счетчика, порождая риск сделать это некорректно.
  3. Ручное управление – Ручное управление памятью это Сишные malloc и free. Это быстро и эффективно по использованию памяти, но язык совершенно не помогает делать все корректно, полностью полагаясь на опыт и усердие программиста. Я использую malloc и free уже 35 лет, и с помощью горького и бесконечного опыта редко делаю ошибки. Но это не тот способ, на который может полагаться технология программирования, и заметьте что я сказал «редко», а не «никогда».

Решения 2 и 3 в той или иной мере полагаются на веру в программиста сделать все корректно. Системы базирующиеся на вере, плохо масштабируются, а ошибки управления памятью доказаны как очень трудно перепроверяемые (настолько плохо, что некоторыми стандартами кодирования использование динамической памяти запрещено).

Но есть еще и четвертый путь — владение и заимствование (Ownership and Borrowing, OB). Это эффективно по памяти, так же быстро как ручное управление, и подлежит автоматизированной перепроверке. Способ недавно популяризирован языком программирования Rust. У него тоже есть свои недостатки, в частности необходимость переосмысления планирования алгоритмов и структур данных.

С негативными сторонами можно бороться и остаток данной статьи является схематичным описанием как система OB работает и как мы предлагаем ее вписать в язык D. Я первоначально считал это невозможным, но затратив на размышления порядочно времени, нашел способ. Он похож на то, как мы поступили с функциональным программированием — с транзитивной иммутабельностью и «чистыми» функциями.

Владение


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

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

T* f();
void g(T*);
T* p = f();
T* q = p; // значение p перемещается в q, а не копируется
g(p);     // ошибка, p более недействительно

Вынесение указателя изнутри структуры данных запрещено:
struct S { T* p; }
S* f();
S* s = f();
T* q = s.p; // ошибка, недопустимо создание двух указателей на s.p

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

Выход владеющего указателя за область видимости тоже является ошибкой:

void h() {
  T* p = f();
} // ошибка, забыли освободить p?

Необходимо перемещать значение указателя по-другому:
void g(T*);
void h() {
  T* p = f();
  g(p);  // переместили в g(), это теперь проблема g()
}

Это прекрасно решает проблемы утечек памяти и использование после освобождения (Подсказка: чтобы было яснее, замените f() на malloc(), и g() наfree().)

Все это может быть проверено на этапе компиляции, используя технику Анализа потока данных (Data Flow Analysis (DFA), примерно как используется для Удаления общих подвыражений. DFA может раскрутить любой крысиный клубок из программных переходов, который может возникнуть.

Заимствование


Система владения, описанная выше, является надежной, но она слишком ограничивающая.
Рассмотрим:

struct S { void car(); void bar(); }
struct S* f();
S* s = f();
s.car();  // s перемещен в car()
s.bar();  // ошибка, s недействителен

Чтобы это заработало, s.car() должна иметь способ вернуть обратно указатель при выходе.

Это то, как работает заимствование. s.car() заимствует копию s на время исполнения s.car(). s недействителен на время выполнения, и становится действительным опять когда s.car() завершится.

В языке D, функции-члены struct получают указатель this по ссылке, так что мы можем приспособить заимствование с помощью небольшого расширения: получение аргумента по ссылке заимствует его.

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

void g(scope T*);
T* f();
T* p = f();
g(p);      // g() заимствует p
g(p);      // мы может использовать p снова по возврату из g()

(Когда функции получают аргументы по ссылке или используются указатели с областью видимости, им запрещено распространяться за границы функции или области видимости. Это соответствует семантике заимствования.)

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

Заимствование может быть расширено далее с пониманием того, что система владения также надежна, даже если на объект указывают дополнительно несколько константных указателей (но только один мутабельный). Константный указатель не может ни изменять память ни освобождать её. Это означает, что несколько константных указателей могут заимствовать у мутабельного владельца, но он не имеет права использоваться пока живы эти константные указатели.

Например:

T* f();
void g(T*);
T* p = f();  // p стал владельцем
{
  scope const T* q = p; // заимствованный константный указатель
  scope const T* r = p; // займем еще один
  g(p); // ошибка, p недействителен пока q и r в области видимости
}
g(p); // ok

Принципы


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

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

Внимательный читатель заметит что то странное в том, что я написал: «как будто». На что я хотел обиняком намекнуть? Что за головоморочка происходит? Да, есть такое. Компьютерные языки программирования полны таких «как будто» под капотом, примерно как деньги на вашем банковском счету на самом деле находятся не там (я прошу прощения, если это был грубый шок для кого-то), и это ничем не отличается от того. Читайте дальше!

Но сначала, чуть углубимся в тему.

Интеграция техники Владения/Заимствования в D


Разве эти техники не несовместимы с тем, как люди обычно пишут на D, и не сломают ли практически все существующие программы на D? Причем не так, что легко поправить, а настолько, что придется перепроектировать с нуля все алгоритмы?

Да, действительно. Разве что D имеет (почти) секретное оружие: атрибуты функций. Оказывается, что семантика владения/заимствования (OB) может быть реализована для каждой функции в отдельности после обычного семантического анализа. Внимательный читатель мог заметить, что никакого нового синтаксиса не добавлено, только наложены ограничения на существующий код. В D уже есть история использования атрибутов функций для изменения их семантики, например атрибут pure для создания «чистых» функций. Для включения семантики OB добавляется атрибут @live.

Это означает что OB может добавляться в код на D постепенно, по мере надобности и свободных ресурсов. Это дает возможность добавлять OB, и это критично, постоянно поддерживая проект в полностью работающем, оттестированном и готовому в выпуску состоянии. Это также позволяет автоматизировать процесс контроля, какой процент проекта уже переведен на OB. Эта техника добавляется в список других гарантий языка D по надежности работы с памятью (таких как контроль нераспространения указателей на временные переменные на стеке).

Как будто


Некоторые необходимые вещи не могут быть реализованы при строгом следовании OB, такие как объекты с подсчетом ссылок. В конце концов RC объекты предназначены для того, чтобы иметь множество указателей на них. Поскольку RC объекты безопасны при работе с памятью (при корректной реализации), они могут использоваться совместно с OB без негативного влияния на надежность. Они только не могут быть созданы при технике OB. Решение в том, что в D имеются и другие атрибуты функций, например @system. @system это функции, где многие проверки надежности отключены. Естественно, OB тоже будет отключено в коде с @system. Это то место, где реализация RC-техники скрывается от контроля OB.

Но в коде с OB, RC объект выглядит так, как будто соблюдает все правила, так что но проблем!

Потребуется некоторое число подобных библиотечных типов для успешной работы с OB.

Заключение


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

Для дальнейших дискуссий и комментариев от Уолтера, обращайтесь к темам на /r/programming subreddit и на Hacker News.

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


  1. gnomeby
    19.07.2019 18:33

    Извините за может быть надоевший вопрос:
    А что вообще в мире на D написано из полезных открытых проектов?


    1. vintage
      19.07.2019 19:56

      1. gnomeby
        19.07.2019 20:12

        Ну эту-то страницу я находил. Вопрос был больше про другое. Где популярные открытые проекты, которые на слуху. Типа docker`а на Go.


        1. vintage
          19.07.2019 20:49
          -1

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


        1. jacob1237
          23.07.2019 18:55

          Вот открытый проект. Популярный, если мерить популярность по звездочкам: https://github.com/gnunn1/tilix


          Вот библиотека для машинного обучения от Netflix:
          https://github.com/Netflix/vectorflow


          Вас какая-то конкретная сфера интересует?


  1. FDA847
    19.07.2019 19:42

    Я так понимаю, что D представляет больше академический интерес. На нём откатывается ряд новых технологий.


    1. snuk182
      19.07.2019 19:47
      +1

      На нём откатывается ряд новых технологий.

      Откатывается — это отличная опечатка.


      1. FDA847
        19.07.2019 19:52

        Да, опечатка. Я имел в виду "обкатывается"


        1. red75prim
          19.07.2019 19:57
          -1

          С GC получилось что-то больше похожее на откат. Начали с GC-based стандартной библиотеки, теперь постепенно переписывают её без использования GC.


        1. snuk182
          19.07.2019 19:59
          +2

          Да понятно. Но в контексте развития языка это неистово веселит. Сначала прыжок из ручного управления памятью в GC, стоивший D будущего, теперь вот эта "новая технология", которая у Rust только в проде уже четыре года, а обкатывалась вообще с 2011-го.


  1. NeoCode
    19.07.2019 20:01
    +3

    D весьма неплох, но последнее время тоже скатывается туда же куда и С++ — куча каких-то надстроек, «секретных атрибутов» и т.п. Изначальная стройность языковой концепции потеряна. Особенно это видно изучая исходники компилятора.

    А по поводу организации памяти (в С++) — я вообще не заморачиваюсь и как правило обхожусь и без GC, и без RC, и без OB. Просто строю архитектуру программы так, что все объекты, создаваемые через new, организованы в единое дерево; у каждого объекта есть единственный владелец, он же отвечает за удаление. Никаких передач владения нет, объект создается в конкретном месте и в этом же месте и удаляется, при этом я свободно передаю указатели на эти объекты в другие места — просто такие указатели считаются «невладеющими» и нигде не хранятся, а просто используются.


    1. PsyHaSTe
      20.07.2019 02:01

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


    1. mikkoljcov
      20.07.2019 12:26
      -1

      Я вот тоже не понимаю этой озабоченности "сборкой мусора". Где выделил память, там и отдай. Хочешь, встрой проверку на уровне редактора: нет free() — нет компиляции. В чем проблема то…


      1. NeoCode
        20.07.2019 23:07
        -1

        На уровне редактора оно разумеется работать не будет — указатель может быть много раз переприсвоен в другие переменные, и передавать во free() будем совсем не то что получили из malloc().
        Что же касается дишных библиотек и их привязки к сборке мусора — тут интересный вопрос: можно ли сделать библиотеки универсальными, такими которые бы содержали код, независимый от наличия или отсутствия сборки мусора в проекте.


  1. phantom-code
    19.07.2019 21:23

    У меня двоякое отношение к языку D. С одной стороны он имеет множество очень интересных возможностей и писать на нем весьма приятно и продуктивно. Но с другой стороны, создается впечатление, что язык в принципе довольно плохо проработан. С точки зрения C++ разработчика, в D есть несколько нелепостей, от которых сильно «бомбит». Например квалификатор const. В D отсутствует аналог mutable из C++ и при этом константность транзитивна. Т.е. если указатель константный, значит и данные, на которые он ссылается, тоже изменять нельзя. Из-за этого в константном методе невозможно реализовать ленивую инициализацию, инкремент счетчика ссылок или даже захватить мьютекс. Общепринятая практика в D — по возможности не использовать const вообще. Еще пример — в D нельзя передать rvalue в функцию, которая принимает ссылку на const данные. Из-за этого приходится создавать временные объекты или плодить перегрузки функций в геометрической прогрессии. Так же в D есть квалификаторы типа shared, scope, которые имеют свои проблемы. Ну и самое «вкусное» — это конечно сборщик мусора. Я нисколько не против его наличия, но в случае необходимости его отключения, придется расстаться и с половиной «стандартной» библиотеки phobos. Кроме того, отвалятся встроенные динамические и ассоциативные массивы, а так же классы. Т.е. по сути, существенная часть возможностей превращается в тыкву.


    1. vintage
      19.07.2019 22:00

      константном методе невозможно реализовать ленивую инициализацию

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


      нельзя передать rvalue в функцию, которая принимает ссылку на const данные. Из-за этого приходится создавать временные объекты или плодить перегрузки функций в геометрической прогрессии.

      https://dlang.org/spec/function.html#inout-functions
      Это не то, что вам нужно?


      Так же в D есть квалификаторы типа shared, scope, которые имеют свои проблемы.

      Какие?


      1. zelyony
        20.07.2019 02:14
        +1

        https://youtu.be/TkNep5zHWNw?t=1489
        о shared выступающий тоже упомянул
        для shared переменных можно вызывать только shared методы, а это тупо удваивает количество кода, и никто их не пишет, т.е. от shared только головняк


        еще


        • инициализация по умолчанию для всего "все нули", но для плавающих точек и символов NaN и '\xFF' соотвественно, но оба они не false. см ниже про bool
        • bool это int1 со всеми вытекающими
        • вместе с ним странный int promotion
          enum Week: int { Mon,… Sun };
          auto fn( bool v ) { }
          auto fn( long v ) { }
          fn( Week.Mon ); // fn( false ). WTF?
        • нет string interpolation; именованных параметров; встроенных кортежей (те что из либки не поддерживают деконструкцию); встроенных null-типов как int? с полезными к ним операторами !, ?., ??; нет стандартных асинков; ctRegex иногда дохнет вместе с компилятором, потому что ему не хватает 174GB памяти
        • про асинки. если асинки будут на goroutine-ах, то из них нельзя обращаться к глобальным переменным, потому что они лежат в TLS (если не shared). спорное решение пихать их в TLS
        • лямбда с { return } возвращает новую лямбду, а не просто значение
          () => { float acc;… return acc; } возвращает float delegate(), а не просто вычисленный float. WTF?
          как вернуть из непростой лямбды простое значение? ХЗ
        • в каждую функцию засовывают десяток импортов. потому что в перегружнных функциях рассматриваются вообще только локальные определения. опять головняк
        • нет версионности модулей, потому изобретают experimental модули, которые ты можешь заюзать, но в след релизе они могут уйти куда-то еще и импорты надо переделывать
        • в стандартной либке нет ни Set, ни Stack, ни Deque. есть массив и хэш-словарь. да, из них можно сделать нужное с ошибками и матами, но сразу их выдать нельзя что ли?
        • с nogc/betterC превращается в тыкву, потому что для nogc вообще ничего нет, кроме шаблонов: ни массивов/строк, ни словарей. с чем работать шаблонам и range-м?
        • есть const & immutable, но нет mutable. бредят RefCount, но как его делать?
        • в винде нельзя заюзать рантайм как дллку стандартным путем. если приложение состоит из EXE и DLL, то в приложении 2 рантайма и 2 помойщика. помойщиков можно смерджить стандартным же костылем. но в чем причина не костылить, а скомпилить shared runtime?
        • консервативный помойщик, который можно заменить доп.шагами в точный. но и он сканит весь data seg и иногда вылетает по нехватке памяти, потому что любой лонг в нем считает за возможный указатель. хотелось бы, чтобы сканил только реальные указатели, инфа о типах есть же
        • нет номеров ошибок, которые можно было бы погуглить, потому что текст ошибок меняется, и не всегда понятно, в чем именно ошибка
        • ошибки с шаблонами и mixin-ами выводят простыни текста, попробуй их пойми
        • диктатор не переводит DMD на LLVM, а потому его не колышат ни interop c С++, ни динамическая компиляция и встроенные скрипты, ни WASM


        1. vintage
          20.07.2019 10:37
          +1

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

          Вызывать non-shared методы на shared данных не безопасно. Вызывать shared методы на non-shared данных медленно.


          от shared только головняк

          Межпоточное взаимодействие — это в принципе головняк. Не занимайтесь им, если не готовы.


          инициализация по умолчанию для всего "все нули", но для плавающих точек и символов NaN и '\xFF' соотвественно

          NaN вполне логичен. Максимальное значение для code points видимо для того, чтобы это был не валидный codepoint. Вы немного не правильно понимаете "значение по умолчанию". Это не "все нули", а "значение, несущее меньше всего информации".


          bool это int1 со всеми вытекающими

          Какими вытекающими? А вы что ожидали? int64?


          вместе с ним странный int promotion

          Это даже не int promotion. Просто значение энума на этапе компиляции заменяется на число 1, а bool может быть инициализирован 0 и 1, поэтому и матчится на эту сигнатуру. Но поведение всё равно странное, согласен.


          нет string interpolation

          http://semitwist.com/scriptlike-docs/v0.10.3/scriptlike/core/interp.html
          Не так красиво, как в каком-нибудь JS, но всё же.


          именованных параметров

          Боюсь красиво вписать эту фичу задача не из простых, а профита от неё не очень-то и много.


          встроенных кортежей (те что из либки не поддерживают деконструкцию);

          Деструктуризация в другой либке:
          https://github.com/valmat/vest#tie


          встроенных null-типов как int?

          https://dlang.org/phobos/std_typecons.html#Nullable


          с полезными к ним операторами !, ?., ??

          Эти операторы уже заняты для другого.


          нет стандартных асинков

          И не надо. Есть куда более удобная штука — волокна.
          https://dlang.org/phobos/std_concurrency.html


          ctRegex иногда дохнет вместе с компилятором

          Это на какой регулярке?


          () => { float acc;… return acc; } возвращает float delegate(), а не просто вычисленный float. WTF?
          как вернуть из непростой лямбды простое значение? ХЗ

          Убрать стрелочку: () { float acc;… return acc; }
          Если аргументов нет, то и круглые скобки можно убрать: { float acc;… return acc; }
          Вы же фактически создаёте два вложенных делегата: через стрелочку и через фигурные скобки. Это не JS :-)


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

          Звучит как бред. Покажите код.


          нет версионности модулей, потому изобретают experimental модули

          Я так понимаю речь про стандартную библиотеку? да, было бы классно, чтобы её вынесли в DUB, а не везли вместе с компилятором.


          в стандартной либке нет ни Set, ни Stack, ни Deque. есть массив и хэш-словарь. да, из них можно сделать нужное с ошибками и матами, но сразу их выдать нельзя что ли?

          alias Set( Item ) = bool[Item];
          alias Stack( Item ) = Item[];
          alias Deque( Item ) = Item[];

          При желании можно запретить ненужные методы. Какие тут ошибки и маты?


          для nogc вообще ничего нет, кроме шаблонов: ни массивов/строк, ни словарей. с чем работать шаблонам и range-м?

          https://dlang.org/phobos/std_container_array.html


          есть const & immutable, но нет mutable. бредят RefCount, но как его делать?

          Не очень понял вашу проблему. Всё, что не помечено как immutable — mutable.


          1. zelyony
            20.07.2019 11:37
            +1

            нет кармы на плюсик


            • про tie() для деконструкции кортежей не знал
            • ctRegex https://issues.dlang.org/show_bug.cgi?id=12844
            • Set, Stack, Deque, который вы показали — "ну, нахер"
            • mutable: есть immutable, который же и shared, передаете его куда-нибудь, RC надо поменять (addref/release), но у immutable это надо делать через извращения с cast-ами. с mutable из С++ это делается красиво и понятно.
            • версионность модулей на уровне импортов
              import core.memory!2.0: memcpy; // юзаешь новые функции и "того" же модуля другой версии. нет головняка с std.experimental, который в будущем уйдет. нет name hell. да, фобос должен стать выборочным и скачиваемым. что делать с shared RT, когда все скачиваемо по отдельности?
            • 2 лямбды одной стрелочкой все равно криво. лучше 2 лямбды 2 стрелочками. так более явно видно
              () => { float acc;… return () => acc; } // я возвращаю лямбду из лямбды
            • fiber ну никак не помогает с async/await (слышал про vibe.d)
            • про бред с импортами: неочевидные правила перегрузок методов https://dlang.org/articles/hijack.html
              есть A.fn( long ) и B.fn( real ), включаешь их в модуль C, в котором есть еще fn( string ) и на С.main() { fn( 123 ) } — ошибка "не могу 123 передать как стринг". WTF? вон же методы, хочешь long, хочешь real, "да тут ГТО можно сдавать!". я зря их писал что ли? странно это.
              ну, и некоторые пишут код так, что в каждом методе импортируют все используемые сущности из других модулей по отдельности — куча импортов в методе, за которыми леса не видно.
              импорт в шапке, как в C#, на весь остальной модуль, просто и понятно.

            на этом закрою дискуссию, добавить больше нечего.
            я юзаю D, но у меня есть к нему претензии.
            юзал бы Go/Rust, к ним бы тоже были претензии.
            а C#/Kotlin нра без претензий :)


            1. vintage
              20.07.2019 15:46

              Ознакомьтесь, пожалуйста, с документацией по лямбдам и делегатам: https://dlang.org/spec/expression.html#function_literals


              async/await ненужен вообще и помощь с ним не нужна никакая. При чём тут vibe.d я не понял.


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


            1. jacob1237
              23.07.2019 17:42

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


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


              D все-таки системный язык, позволяющий делать очень многое, и насильно привязывать его к таким конструкциям как async/await смысла нет


          1. jacob1237
            23.07.2019 17:35

            От https://dlang.org/phobos/std_typecons.html#Nullable толку как от козла капусты.
            Да, он есть, но в компайл тайме все равно не ограничивает от неправильного использования.


            Лучше уж тогда Optional брать из другой библиотеки.


        1. dayllenger
          21.07.2019 20:55

          Хочу добавить 3 вещи.

          Импортировать прямо в функции нужно только для того, чтобы: а) уменьшить время компиляции в шаблонном коде (тот же phobos) из-за неиспользованных импортов, б) импортировать ближе к месту использования. В остальном это тупая трата экранного места.

          Для стека или множества можно взять что-нибудь из std.container.

          Ошибки с шаблонами недавно получили апгрейд, так что теперь они должны стать читабельней.


        1. jacob1237
          23.07.2019 18:47

          Кстати проблемы с ctRegex должны будут решиться с релизом проекта newCTFE: https://dlang.org/blog/2017/04/10/the-new-ctfe-engine/


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


  1. freecoder_xx
    22.07.2019 18:11

    Так, ну а лайфтаймы завезут?


    1. vintage
      22.07.2019 19:24

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


      1. Siemargl Автор
        22.07.2019 22:11

        Ну в D и так неплохо с лайфтаймами, так что доработки вряд ли будут большими.