В предисловии к книге А. Александреску «Язык программирования D» Уолтер Брайт пишет, что начал разработку этого языка в 1999 году. Проект задумывался как реинжиниринг языка C++ с целью избавиться от наиболее существенных недостатков исходного языка и внедрить в него современные архитектурные решения. При создании языка D была сделана попытка соединить производительность компилируемых языков программирования с безопасностью и выразительностью динамических.
Первоначально автор предполагал назвать язык «Mars», но из-за преемственности по отношению к C++ в обсуждениях язык постоянно называли «D», в результате именно это название и закрепилось за проектом.
Стабильная версия компилятора 1.0 вышла 2 января 2007[4]. Вскоре после выхода компилятора 17 июня 2007 года автор перевёл версию 1 в режим поддержки и приступил к разработке версии 2.0, которая изначально не гарантировала обратной совместимости[5]. Эта версия (последняя на сегодняшний день мажорная версия D) развивается и по сей день.
Обзор языка
В языке D реализовано множество синтаксических средств и концепций, отсутствующих в C++: контрактное программирование, встроенные юнит-тесты, модули вместо заголовочных файлов (до C++ 20), поддержка сборки мусора (при сохранении доступности ручного управления памятью), встроенные ассоциативные массивы, замыкания, анонимные функции, значительно переработан механизм шаблонов.
Синтаксис
D относится к семейству C-подобных языков, в общих чертах его синтаксис похож на C/C++/C#, Java. При разработке языка соблюдается принцип: код, одинаково валидный и в C, и в D, должен вести себя одинаково.
Так же, как в C, функция main() является точкой входа.
Конструкции if, for, while, do-while выглядят и работают аналогично C/C++. Инструкция множественного выбора switch выглядит аналогично C++, но допускает переменные в метках ветвей case и требует, чтобы каждая ветвь case завершалась break или return; для перехода на следующую ветвь после обработки текущей необходимо использовать специальную конструкцию goto case. Также запрещены конструкции switch без ветви default.
Из дополнительных управляющих конструкций можно отметить static if — инструкцию для условной компиляции (условие проверяется статически и в код включается содержимое той ветви, которая ему соответствует), оператор полного множественного выбора final switch — в отличие от обычного switch, он работает только со значениями enum, а компилятор статически проверяет, что в выборе учтены все возможные варианты и выдаёт ошибку в противном случае. Также есть цикл по коллекции foreach.
Модульность
В D встроена система разбиения программы на модули (пакеты), обеспечивающая раздельную компиляцию и контролируемый импорт-экспорт. Система пакетов напоминает принятую в Java или Go: пакеты образуют иерархическую структуру, естественно отображаемую на дерево файловой системы. В отличие от C++, в D нет глобального пространства имён, каждое имя определяется в каком-либо пакете. С помощью инструкции import модуль программы может импортировать пакет, сделав доступными все имеющиеся в нём определения. Обращение к импортированным именам может выполняться с квалификацией: «имя_пакета.имя_объекта».
Язык предусматривает ряд средств, направленных на обеспечение удобной работы с импортируемыми именами. Есть возможность переименования пакета при импорте, задания альтернативного имени (алиаса) импортируемого пакета, импорта конкретных имён. Кроме того, язык разрешает без каких-либо дополнительных инструкций использовать импортированные имена без квалификации именем пакета. Однако действует ограничение: если в области видимости есть более одного подходящего определения встреченного в программе имени, то компилятор выдаёт ошибку и требует, чтобы имя было явно квалифицировано. Это предотвращает так называемый «угон имён», когда при добавлении в списки импорта нового пакета компилятор начинает связывать некоторое имя в программе не с тем определением, с которым оно связывалось ранее.
Универсальный синтаксис вызова функций (UFCS)
В D реализован механизм UFCS (Uniform function call syntax), позволяющий вызывать функции для любого объекта так, как будто они являются его методами. Например:
importstd.stdio;importstd.algorithm;importstd.array;voidmain(){autoa=[2,4,1,3];// все три следующих варианта корректны и работают одинаковоwriteln(a);// "классический" C-подобный вариантa.writeln();// функция вызывается так, как будто является методом объекта "a", хотя и не является таковойa.writeln;// функцию без параметров можно вызывать без скобок// это позволяет использовать цепочки вызовов, характерные для функциональных языковint[]e=a.sort().reverse;// многострочная цепочка вызовов также возможнаstdin.byLine(KeepTerminator.yes).map!(a=>a.idup).array.sort;}
Атрибуты функций
Функции в D могут быть определены с дополнительными необязательными атрибутами, которые позволяют явно указывать некоторые аспекты поведения этих функций. Например, функция, помеченная атрибутом pure, гарантированно является функционально чистой (с некоторыми оговорками)[6]. Функциональная чистота при этом проверяется на этапе компиляции. Пример объявления функции с атрибутом:
pureintsum(intfirst,intsecond){returnfirst+second;}intsum(intfirst,intsecond)pure// атрибуты можно указывать и после списка аргументов{returnfirst+second;}
Примеры атрибутов функций:
pure — функциональная чистота
@safe — гарантия безопасной работы с памятью
nothrow — функция гарантированно не генерирует исключений
@nogc — гарантия того, что функция не содержит операций, выделяющих память на сборщике мусора
@property — атрибут метода класса, позволяющий избежать использования «наивных» геттеров-сеттеров
Параллельные вычисления
В язык встроен механизм запуска параллельных подпроцессов с помощью встроенной функции spawn() и обмена данными между параллельно исполняемыми фрагментами кода путём передачи сообщений (функции send() и receive() / receiveTimeout()). Использование обмена сообщениями считается авторами D предпочтительнее обмена данными через общую память.
Тем не менее, в случаях, когда это необходимо (например, при передаче между сопрограммами больших объёмов данных) имеется возможность применять традиционный для императивных языков подход с разделяемыми областями памяти и синхронизацией доступа через семафоры и мьютексы. Для поддержки такого обмена:
c помощью модификатора shared могут объявляться данные, разделяемые между потоками, при этом на уровне компилятора блокируются попытки работать с этими данными не-атомарными операциями;
библиотека языка предоставляет стандартные синхронизационные примитивы;
библиотека языка предоставляет специальные блокировочные классы, которые используются для контекстной блокировки: объявление экземпляра такого класса в методе синхронизирует код от данной точки до конца метода;
с помощью модификатора synchronized можно объявить целый класс синхронизированным: все методы этого класса компилятор автоматически синхронизирует, так что никакие два из них не могут выполняться параллельно для одного и того же экземпляра класса, объявленного как shared.
Для всех встроенных средств синхронизации компилятор автоматически отслеживает и запрещает попытки изменять внутри синхронизируемого кода неразделяемые данные, доступные более чем одному потоку.
Встроенные юнит-тесты
В D юнит-тесты являются частью языка, их можно использовать без подключения дополнительных библиотек или фреймворков.
D использует сборщик мусора для управления памятью, однако возможно и ручное управление с помощью перегрузки операторов new и delete, а также с помощью malloc и free, аналогично C. Сборщик мусора можно включать и выключать вручную, можно добавлять и удалять области памяти из его видимости, принудительно запускать частичный или полный процесс сборки. Существует подробное руководство, описывающее различные схемы управления памятью в D для тех случаев, когда стандартный сборщик мусора неприменим.
Язык имеет богатый набор определённых типов данных и средств для определения новых типов. Типы в языке D разделяются на типы-значения и типы-ссылки.
Базовые типы
Набор базовых типов можно разделить на следующие категории[7]:
void — специальный тип для пустых значений
bool — логический тип
целочисленные типы: знаковые byte, short, int, long и соответствующие им беззнаковые ubyte, ushort, uint, ulong
типы для чисел с плавающей точкой: float, double, real. Для типов с плавающей точкой есть соответствующие им варианты для мнимых и комплексных чисел:
мнимые: ifloat, idouble, ireal
комплексные: сfloat, сdouble, сreal
знаковые (символьные) типы: char, wchar, dchar, обозначающие кодовые единицы кодировок UTF-8, UTF-16 и UTF-32 соответственно.
В отличие от C++ все размеры целочисленных типов определены спецификацией. То есть, тип int будет всегда размером 32 бита.
Целочисленные литералы можно записывать в десятичной, двоичной (с префиксом 0b) и шестнадцатеричной (с префиксом 0x) системе счисления.
Способ записи литералов в восьмеричной системе в стиле C (то есть с префиксом 0) был убран, так как такую запись легко спутать с десятичной.
Если всё-таки нужно использовать восьмеричную систему, можно воспользоваться шаблоном std.conv.octal.
Производные типы
pointer — указатель
array — массив
associative array — ассоциативный массив
function — функция
delegate — делегат
string, wstring, dstring — удобные псевдонимы для неизменяемых массивов знаковых (символьных) типов immutable(char)[], immutable(wchar)[] и immutable(dchar)[], обозначающие неизменяемые (квалификатор immutable) строки Юникода в одной из кодировок UTF-8, UTF-16 и UTF-32 соответственно.
Пользовательские типы
alias — псевдоним
enum — перечисление
struct — структура
union — объединение
class — класс
Вывод типов, ключевые слова «auto», «typeof» и безымянные («Voldemort») типы
В D реализован механизм вывода типов. Это значит, что тип, как правило, может быть вычислен на этапе компиляции и его не обязательно указывать явно. Например, выражение:
auto myVar = 10 на этапе компиляции будет преобразовано в int myVar = 10.
Использование вывода типов дает несколько преимуществ:
Более лаконичный и читаемый код, особенно если в нём используются длинные имена структур или классов. Например, выражение
VeryLongTypeName var = VeryLongTypeName(/* ... */);
может быть заменено на
auto var = VeryLongTypeName(/* ... */);
с помощью ключевого слова typeof можно создать переменную такого же типа, как у существующей переменной, даже если её тип неизвестен. Пример:
// file1.dintvar1;// file2.dtypeof(var1)var2;// var2 получает тип int
использование безымянных типов. Пример:
// Функция фактически возвращает результат типа TheUnnameable, но, поскольку этот тип определен внутри функции, // мы не можем явно задать его как тип возвращаемого значения.// Тем не менее, мы можем задать возвращаемый тип как "auto", предоставив компилятору вычислить его самостоятельноautocreateVoldemortType(intvalue){structTheUnnameable{intgetValue(){returnvalue;}}returnTheUnnameable();}
Безымянные типы неофициально называются Voldemort-типы по аналогии с Воланом-де-Мортом («Тот-Кого-Нельзя-Называть»), главным антагонистом серии о Гарри Поттере[8].
Вывод типов не следует путать с динамической типизацией, поскольку хотя тип не задается явно, вычисляется он на этапе компиляции, а не во время выполнения.
Реализации
DMD — Digital Mars D (ВИКИ), эталонный компилятор, разрабатываемый Уолтером Брайтом. Этот компилятор наиболее полно реализует стандарт языка, поддержка всех нововведений появляется в нём в первую очередь. Фронт-энд и бэк-энд ([1]) распространяются под лицензией Boost.
DIL is a hand-crafted compiler implementation for the D programming language written in D v2 using the Tango standard library. The lexer and the parser are fully implemented. Semantic analysis is being worked on. The backend will most probably be LLVM.
Инструменты и средства разработки
IDE и редакторы
Поддержка D в различных IDE, реализованная с помощью плагинов:
DLangIDE (Windows, Linux, OS X) — среда создана на самом D, поддерживает подсветку синтаксиса, автозавершение кода, DUB, различные компиляторы, отладку и многое другое.
D поддерживается во множестве текстовых редакторов: Vim, Emacs, Kate, Notepad++, Sublime Text, TextMate и других[11].
Менеджер пакетов
DUB — официальный менеджер пакетов D. DUB выполняет функции репозитория пакетов и используется для управления зависимостями, а также в качестве системы сборки. Набор зависимостей, метаданные о проекте и флаги компилятора хранятся в формате JSON или SDL.
Пример простого файла проекта (JSON):
{"name":"myproject","description":"A little web service of mine.","authors":["Peter Parker"],"homepage":"http://myproject.example.com","license":"GPL-2.0","dependencies":{"vibe-d":"~>0.7.23"}}
Утилиты и инструменты
rdmd — утилита, идущая в комплекте с компилятором DMD, позволяющая компилировать и запускать файлы с исходным кодом D «на лету». Это позволяет использовать D для небольших программ аналогично bash, perl и python:
// myprog.d#!/usr/bin/envrdmdimportstd.stdio;voidmain(){writeln("Hello, world with automated script running!");}
Вызов команды ./myprog.dв консоли автоматически скомпилирует и выполнит программу.
DPaste[12] — онлайн-сервис для запуска программ на D в браузере, похожий на сервисы JSBin и CodePen.
run.dlang.io[13] — онлайн-компилятор и дизассемблер.
Использование, распространение
Распространение языка D ограничено, но он применяется для реальной промышленной разработки ПО. На официальном сайте[14] приводится список из 26 компаний, успешно использующих D в разработке программных систем, работающих в самых различных сферах, включая системное программирование, веб-проекты, игры и игровые движки, ПО для научных расчётов, утилиты различного назначения и прочее. Продвижением языка D занимается, в частности D Language Foundation — общественная организация, пропагандирующая сам язык D и открытое программное обеспечение, созданное с его использованием.
Согласно индексу TIOBE, максимальный интерес к D проявлялся в 2007—2009 годах, в марте 2009 года индекс языка D достиг 1,8 (12-е место), что является для него абсолютным максимумом. После снижения в первой половине 2010-х, к 2016 году он пришёл в относительно стабильное состояние — численное значение индекса колеблется в диапазоне 1,0-1,4, в рейтинге язык находится в третьей десятке. В рейтинге популярности, составленном по результатам агрегации данных о вакансиях разработчиков, язык D не входит ни в основной (top-20), ни в общий (top-43) список, что говорит о невысокой востребованности у работодателей.