D (мова програмування)

D - об'єктно-орієнтований, імперативний, мультипарадигмальной мова програмування, створений Уолтером Брайтом з компанії Digital Mars. Спочатку був задуманий як реінжиніринг мови C + +, однак, незважаючи на значний вплив С + +, не є його варіантом. У D були заново реалізовані деякі властивості C + +, також мова зазнала вплив концепцій з інших мов програмування, таких як C #, Eiffel.

При створенні мови D була зроблена спроба поєднати продуктивність компільованих мов з безпекою і виразністю динамічних мов програмування. Код на мові D зазвичай працює так само швидко як еквівалентний код на C + +, при цьому програма на D коротше і забезпечує безпечний доступ до пам'яті.

Стабільна версія компілятора 1.0 вийшла 2 січня 2007 [3]. Експериментальна версія компілятора 2.0 випущено 17 червня 2007 року. [4]

Стабільна версія компілятора 1.0 працює на Windows, Linux, Mac OS, а з версії 1.043 також і на FreeBSD. Також для завантаження доступні вихідні коди DMD (офіційна реалізація компілятора від Digital Mars) [5].


D - мова програмування загального призначення, призначений для прикладного та системного програмування. Розроблений Уолтером Брайтом і його фірмою Digital Mars. D є мовою високого рівня, але зберігає можливості прямої взаємодії з програмним інтерфейсом операційної системи і з обладнанням [6]. D призначений для написання середніх і великих систем з мільйонами рядків вихідного коду, для ведення командної розробки. Мова D має C-подібний синтаксис, який більш лаконічний і читабельний, ніж синтаксис С + +, для людини, яка знає, наприклад, С, C # або Java [7], підтримує багато можливостей в допомогу програмісту [8] [9] [10 ] [11], а також придатний для проведення непоганий оптимізації коду компілятором [12].


1. Можливості, успадковані від C + +

Синтаксис мови D схожий з синтаксисом C + + і C #, що полегшує його вивчення людям, знайомим з цими мовами, а також перенесення вихідного коду з С + +.

Для D не існує віртуальної машини. Програми, написані на D, компілюються в об'єктні файли, які потім можуть бути об'єднані компонувальником в виконуваний файл. Стандартні інструменти для розробки на С + + (наприклад make) можуть бути використані для D.


2. Що відсутня в мові D

  • Сумісність з вихідним кодом на мові C. Вже існують мови програмування, в повній мірі або практично повністю сумісні з вихідним кодом, написаним на мові C ( Objective-C, C + + і Vala). Автори вважали, що подальша робота в цьому напрямку перешкоджає реалізації істотних можливостей. Однак, вони постаралися не допускати нестандартної поведінки запозичень з C операторів, яке може приводити до важко виправляти помилки.
  • Препроцесор [13]. Включення файлів за допомогою #include замінено на модулі з власним простором імен. Неструктуровані директиви типу #ifdef замінені на структуровані блоки version й debug. [14] Виконання коду під час компіляції, включення будь-яких константних виразів, а також читання файлів під час компіляції можуть у більшості випадків емулювати макро-мову C з більшою надійністю (тим не менше , деякі можливості C втрачені, наприклад, не можна написати аналог #define TEXT(quotes) L ## quotes), а також відкривають безліч додаткових можливостей, наприклад переклад коду іншої мови програмування в D "прямо під час компіляції".
  • Множинне спадкування. Однак це частково компенсується більш надійними інтерфейсами, робота з якими підтримується мовою D.
  • Простори імен (namespaces). Простори імен були спробою вирішити проблему, що виникає при об'єднанні розроблених незалежно один від одного шматків коду, коли перетинаються імена змінних, типів даних і так далі. Модульний підхід виглядає простіше і зручніше для використання.
  • Бітові поля (bit fields) довільного розміру. Бітові поля складні, неефективні та досить рідко використовуються. Компенсується модулем в стандартній бібліотеці D 2.0 [15]
  • Підтримка 16-бітових комп'ютерів. У мові D немає ніяких рішень для генерування якісного 16-бітного коду.
  • Взаємна залежність проходів компілювання (compiler passes). У мові C + + успішна обробка вихідного коду грунтується на таблиці символів (symbol table) і різних командах препроцесора. Це робить неможливим попередню обробку коду і значно ускладнює роботу аналізаторів коду.
  • Оператор разименованія зі зверненням до члена класу ->. У мові D оператор звернення до члена класу виробляє разименованія за замовчуванням при необхідності.

3. Для чого призначений мову D

  • Використання аналізаторів коду;
  • Компіляція з максимальною кількістю включених рівнів попереджень (warning levels);
  • Спрощення та приборкання ООП C + +;
  • Автоматичне керування пам'яттю;
  • Вбудоване тестування та верифікація;
  • Написання великих додатків;
  • Вбудоване автоматичне написання програм;
  • Абстрактна робота з покажчиками;
  • Програмування математики. Мова D підтримує роботу з комплексними числами і операторами визначення NaN'ов (not a number) і нескінченностей (infinity). Вони були додані в стандарт C99, але не були додані в C + +;

4. Для чого D не призначений

  • Для перенесення на нього в незмінному вигляді старого (legacy) коду на С / С + +;
  • Скриптової програмування;
  • Вивчення в якості першої мови програмування.

5. Особливості мови D

5.1. Об'єктно-орієнтоване програмування

5.1.1. Класи

Об'єктно-орієнтована природа мови D походить від класів. Модель спадкування не підтримує спадкування від декількох класів, зате розширюється за рахунок використання інтерфейсів. На вершині ієрархії спадкоємства знаходиться клас Object, від якого всі класи успадковують базовий набір функціональності. Екземпляри класів працюють по посиланню, тому після обробки виключень не потрібно писати складний код для очищення пам'яті.

5.1.2. Перевантаження операторів

Класи можуть бути пристосовані для роботи з уже існуючими операторами. Наприклад, можна створити клас для роботи з великими числами і перевантажити оператори +, -, * і /, щоб використовувати алгебраїчні операції з цими числами.

5.2. Ефективність

5.2.1. Модулі

Файли вихідного коду взаємно однозначно відповідають модулям. Замість включення ( #include) файлів вихідного коду достатньо імпортувати модуль. У цьому випадку немає необхідності турбуватися про те, що один і той же модуль буде імпортований кілька разів, а, значить, і немає необхідності обрамляти код в заголовних файлах з використанням макросів препроцесора #ifndef / #endif або #pragma once. Однак, варто пам'ятати, що статичні конструктори ( static this() { ... }, викликається один раз до функції main() [16]) всіх імпортованих (прямо чи опосередковано) модулів викликаються до статичного конструктора модуля-імпортера, тобто можливі циклічні залежності, які часом важко усунути (наприклад, щось в модулі А залежить від модуля B, а в B - від А). Тоді ініціалізацію модулів доводиться реалізовувати викликами функцій на початку main().


5.2.2. Оголошення проти опису

C + + зазвичай вимагає, щоб функції і класи були оголошені двічі - оголошення відбувається в заголовних файлах (*. H), а опис відбувається в файлах вихідного коду (*. Cpp). Це стомлюючий і схильний помилок процес. Очевидно, що програмісту досить оголосити функцію або клас лише одного разу, а компілятор повинен згодом витягти інформацію про оголошення і зробити її доступною для імпортування. Саме так працює мову програмування D, наприклад:

 class  ABC  {  int  func  (  )  {  return  7  ;  }  static  int  z  =  7  ;  }  ;  int  q  ; 

І більше немає необхідності окремого опису функцій-членів, атрибутів і специфікацій зовнішніх оголошень ( extern), як в мові C + +:

 int  ABC  ::  func  (  )  {  return  7  ;  }  int  ABC  ::  z  =  7  ;  extern  int  q  ; 

Замітка: Звичайно ж, в C + + тривіальні функції начебто {return 7;} теж можуть бути описані усередині класу, але більш складні рекомендується визначати поза класом. До того ж, якщо потрібні випереджаючі посилання (посилання на клас чи функцію, які оголошені, але ще не визначені), то для цих об'єктів потрібні прототипи (prototype). Наступний код не буде працювати в C + +:

 class  Foo  {  int  foo  (  Bar  *  c  )  {  return  c  ->  bar  (  )  ;  }  }  ;  class  Bar  {  public  :  int  bar  (  )  {  return  3  ;  }  }  ; 

Але еквівалентний код на мові D буде робочим:

 class  Foo  {  int  foo  (  Bar c  )  {  return  c.  bar  ;  }  }  class  Bar  {  int  bar  (  )  {  return  3  ;  }  } 

А те, чи буде функція вбудовуваної (при компіляції виклик такої функції замінюється її кодом) чи ні, в мові D залежить від налаштувань оптимізатора.


5.2.3. Шаблони

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

5.2.4. Асоціативні масиви

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

5.2.5. Справжній typedef

У мовах C і C + + оператор typedef насправді просто створює синонім типу даних і ніякого нового типу даних не оголошується. У мові D оператор typedef оголошує новий тип даних. Таким чином, код

 typedef  int  handle  ; 

створює новий тип даних handle. До нового типу даних застосовується перевірка на відповідність типу даних, а також при перевантаженні функцій новий тип даних відрізняється від того типу даних, на основі якого він був створений. Наприклад:

 int  foo  (  int  i  )  ;  int  foo  (  handle h  )  ; 

5.3. Документація

Документування традиційно відбувається в два етапи - написання коментарів, що описують, що роблять функції, а потім перенесення цих коментарів вручну в окремі HTML-файли та файли документації man. І, природно, з часом те, що описується в документації, буде відрізнятися від того, що насправді відбувається у вихідному коді. Можливість генерації документації безпосередньо з коментарів у файлах вихідного коду не тільки економить час при підготовці документації, але й полегшує підтримку відповідності документації вихідного коду. Для мови D існує єдина специфікація для генератора документації.

Для генерації документації в мові C + + існують інструментальні засоби, що розробляються окремо від компіляторів. Використання цих інструментальних засобів має ряд недоліків:

  • Дуже складно обробити C + + код на 100% правильно, для цього насправді потрібно компілятор. Сторонні інструменти (third party tools) коректно працюють тільки з підмножиною мови C + +.
  • Різні компілятори підтримують різні версії мови C + + і різні розширення мови. Дуже важко відповідати всім цим варіаціям підтримки мови C + +.
  • Інструменти для генерування документації з C + + можуть не бути реалізовані для деяких платформ або можуть не відповідати останній версії компілятора.

Будучи вбудованим в компілятор, генерування документації є єдиною для всіх реалізацій мови D.


5.4. Функції

Мова D має підтримку звичайних функцій, включаючи глобальні функції, перевантажені функції, вбудовування функцій, методи класів, віртуальні методи класів, покажчики на функції. Також в D реалізовані:

5.4.1. Вкладені функції

Функції можуть бути оголошені в тілі інших функцій, що дуже корисно для обмеження області видимості допоміжних функцій.

5.4.2. Функціональні літерали (function literals)

Анонімні функції можуть бути вбудовані безпосередньо в вираз.

5.4.3. Динамічні делегування (closure)

Вбудовані функції і методи класів можуть використовуватися при делегуванні обов'язків, що робить узагальнене програмування простіше.

5.4.4. Специфікатори доступу до параметрів функцій: in, out і inout

Дані специфікатори не тільки роблять оголошення функцій говорять самі за себе, але і відкриває можливості компілятора для допомоги в знаходженні помилок.

Всі ці можливості D дозволяють використовувати більше різних програмних інтерфейсів, а також позбавляє від необхідності використовувати різні штучні прийоми, як, наприклад IDL (Interface Definition Languages).

5.5. Масиви

Масиви в мові C мають кілька недоліків, які доводиться коректувати:

  • Інформація про розмірності масиву не додається до масиву, тому повинна вилучатись додатково і передаватися окремо. Класичний приклад - це оголошення функції main(int argc, char *argv[]). У мові D, функція main оголошується так: main(char[][] args);
  • Коли масив передається в функцію, то він неявно перетвориться в покажчик на перший елемент масиву, навіть коли прототип функції говорить, що повинен бути переданий масив. При цьому інформація про розмір масиву втрачається. (Проте, в С + + при передачі масиву по посиланню (а не за значенням) інформація не втрачається).
  • Розмірність масивів мови C не може бути змінена. Це означає, що навіть така проста структура даних, як стек, повинна бути реалізована у вигляді складного класу;
  • У мові C не можна зробити перевірку на порушення меж масиву, просто тому, що розмірність масиву невідома (однак її реалізація сильно уповільнює роботу програми);
  • Масиви оголошуються з використанням оператора [] після назви масиву. Це веде до використання дуже незграбного синтаксису при оголошенні, скажімо, посилання (покажчика - в термінах C) на масив:
 int  (  *  array  )  [  3  ]  ; 

У мові D оператор [] при оголошенні масиву ставиться після типу даних:

 int  [  3  ]  *  array  ;  / / Оголошується посилання на масив із трьох цілих чисел  long  [  ]  func  (  int  x  )  ;  / / Оголошується функція, що повертає масив довгих цілих 

Цей код більш легкий для сприйняття. У мові D можна використовувати кілька видів масивів: покажчики, статичні масиви, динамічні масиви і асоціативні масиви.


5.5.1. Рядки

Робота з рядками потрібна в більшості сучасних програм, так чи інакше працюють з текстом. Підтримка рядків в мові С - вельми низькорівнева, а C + + зберігає сумісність з ним, додаючи більш просунуті механізми роботи з рядками, як спеціальними контейнерами даних в стандартній бібліотеці STL. Деякі з популярних бібліотек, наприклад, Qt, додають свої типи даних для зберігання рядків (напр. для кращої підтримки юникода). Тому, при програмуванні на С + + бувають ситуації, коли програмісту доводиться одночасно працювати з рядками 2-х - 3-х типів. Щоб уникнути цієї ситуації - робота з рядками в мові D переглянута.


5.6. Управління ресурсами (resource management)

5.6.1. Автоматичне керування пам'яттю

Виділення пам'яті в мові D повністю контролюється методикою "збірки сміття". Досвід показує, що більшість складних можливостей мови C + + вимагають подальшого звільнення пам'яті. Методика "збірки сміття" робить життя простішим. Існує думка, що механізм "збірки сміття" потрібен тільки ледачим і починаючим програмістам. Зрештою, в C + + немає нічого такого, чого не можна було б зробити в C або в асемблері. "Збірка сміття" позбавляє від стомлюючого написання коду, що відслідковує процес виділення пам'яті, який, до того ж, може бути підданий появі помилок. Звичайно, "збірку сміття" можна використовувати і в C + +, але ця мова не дружній по відношенню до "збирачам сміття", що не може не позначитися на ефективності "збірки сміття".


5.6.2. Явне управління пам'яттю

Незважаючи на те, що мова D підтримує автоматичну збірку сміття, оператори new і delete можуть бути перевантажені для певних класів.

5.6.3. RAII

RAII - це сучасна методика розробки для управління розподілом і звільненням ресурсів. Мова D підтримує методику RAII в контрольованій і передбачуваною манері, незалежної від циклу збірки сміття.

5.7. Продуктивність

5.7.1. Легковагі складові типи даних

Мова D підтримує прості структури в стилі мови C не тільки для сумісності з цією мовою, але й тому що вони дуже корисні в тих випадках, коли можливостей класів занадто багато.

5.7.2. Вбудований асемблер

Драйвери пристроїв, високопродуктивні системні програми, а також вбудовувані системи (embedded systems) потребують поглиблення до рівня команд асемблера. Програмування на мові D не вимагає використання асемблера, але він реалізований і є частиною мови.

5.7.3. Налагоджувальний код

Тепер відладка є частиною мови. Налагоджувальний код може бути включений або відключений перед компілювання, при цьому не потрібно використовувати макроси або команди препроцесора. Такий варіант використання налагодження робить код несуперечливим, стерпним і забезпечує розуміння того, що один і той же код використовується для компіляції та налагоджувальних версій програми і релізів.

5.7.4. Обробка виключень

Модель try-catch-finally переважніше, ніж просто try-catch, тому що не вимагає створення тимчасових (dummy) об'єктів, деструктор якого буде виконувати те, що може зробити finally.

5.7.5. Синхронізація

Багатопотокове програмування стає все більш поширеним, тому в мові D реалізовані базові можливості для створення багатопоточних додатків. Синхронізація може бути застосована до всього об'єкту або до окремих його методам.

 synchronized  int  func  (  )  {  ...  } 

Синхронізовані функції дозволяють в один момент часу виконувати код функції тільки одному потоку.

Стійкі до помилок методики

  • Динамічні масиви замість покажчиків;
  • Змінні-посилання замість покажчиків;
  • Посилання на об'єкти замість покажчиків;
  • Збірка сміття замість явного управління пам'яттю;
  • Вбудовані можливості синхронізації потоків;
  • Вбудовувані функції замість макросів;
  • Зменшення необхідності використовувати покажчики;
  • Явні розміри цілого типу даних;
  • Відсутність невизначеності, що стосується наявності знака у символьного типу;
  • Відсутність необхідності повторного оголошення;

Перевірки під час компіляції

  • Більш сувора перевірка на відповідність типу даних;
  • Ніяких порожніх умов в циклі for;
  • Присвоєння не повертають булевого значення;
  • Перевірка використання застарілих API;

Перевірки під час виконання

  • Вирази assert ();
  • Перевірка на вихід за межі масиву;
  • Виняток при нестачі пам'яті;

5.8. Сумісність

5.8.1. Пріоритети операторів і правила обчислення

Мова D зберіг оператори з мови C, а також їх пріоритети і правила обчислення. Це дозволить уникнути ситуацій, коли оператори ведуть себе несподіваним чином (тим не менше Керніган та Рітчі, автори мови C, визнають, що десь пріоритет операторів в цій мові нелогічний [17]).

5.8.2. Прямий доступ до API мови C

Мова D не тільки має типи даних, які відповідають типам даних мови C, але і забезпечує прямий доступ до функцій мови C. У такому випадку немає необхідності писати функції-обгортки (wrapper functions) або копіювати значення членів складових типів по одному.

5.8.3. Підтримка всіх типів даних мови C

Це робить можливим взаємодію з API мови C або з кодом існуючої бібліотеки мови C. Ця підтримка включає структури, об'єднання, перерахування, покажчики і всі типи даних, введені в стандарті C99.

5.8.4. Обробка виключень операційної системи

Механізм обробки виключень мови підключається до механізмів обробки виключень операційної системи.

5.8.5. Використання існуючих інструментаріїв

Код на мові D перетвориться в об'єктний файл стандартного формату, що робить можливим використання стандартних асемблер, компонувальник, отладчиков, Профайлер і компресорів виконуваних файлів.

5.9. Управління проектом

5.9.1. Контроль версій

У мові D реалізована вбудована підтримка генерування декількох версій програм з одного вихідного коду. Це замінює використання команд препроцесора #if і #endif.

5.9.2. Старіння

Код з часом змінюється, тому старий код з часом замінюється новим, поліпшеним. Стара версія коду повинна бути доступна для зворотної сумісності, але може бути відзначена як не рекомендована до використання (deprecated). Це полегшує роботу команди підтримки за визначенням залежностей від застарілого коду.

5.9.3. Відсутність попереджень (warnings)

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

6. Відсутність макросів

Відмова творців мови від препроцесора (як, наприклад, в мові Сі) багато хто розцінює як ризикований і невірний крок. Але в D є вбудовані засоби, які дозволяють обходитися без препроцесора. На думку багатьох, препроцесор C надто потужний і небезпечний.

Реалізація на мові Сі Реалізація на мові D
 # If! Defined (SOME_FILE_H)  / / # Ifndef SOME_FILE_H  # Define SOME_FILE_H  / / Код  # Endif 
 / / Оголошення постійної  # Define MAX_INT 32767  / / Створення псевдонімів  # Define true TRUE  # If defined (Mac_OS_X)  / / Код для Mac  # Else  / / Інакше  # Endif 
 / / Оголошення постійної  enum  uint  MAX_INT  =  32767  / / Створення псевдонімів  alias  TRUE  true  version  (  Mac OS X  )  {  / / Код для Mac  }  else  {  / / Інакше  } 

7. Стандартна бібліотека

На відміну від багатьох інших мов, в D два стандартні бібліотеки : Phobos і Tango. Phobos поставляється разом з компілятором.

Існування двох різних стандартних бібліотек створює деякі труднощі. Для написання коду, що працює з обома бібліотеками, можна використовувати наступну конструкцію:

 version  (  Tango  )  {  / / Код, що працює з функціями Tango  }  else  {  / / Код, що працює з функціями Phobos  } 

7.1. Tangobos

Для того щоб вирішити проблеми з бібліотеками, був запущений проект Tangobos. Tangobos - обгортка Tango, що дає програмісту інтерфейс Phobos.

8. Приклади

Ця програма друкує аргументи командного рядка. Функція main є точкою входу програми, а args - масив з параметрами запуску програми.

 module  main  ;  / / Бажано оголошувати  version  (  Tango  )  import  tango.  io  .  Stdout  ;  else  import  std.  stdio  ;  / / Для writefln ()  void  main  (  char  [  ]  [  ]  args  )  {  foreach  (  int  i  ,  char  [  ]  a  ;  args  )  {  version  (  Tango  )  Stdout.  formatln  (  "Args [{}] = {}"  ,  i  ,  a  )  ;  else  writefln  (  "Args [% d] = '% s'"  ,  i  ,  a  )  ;  }  } 

Примітки

  1. Список змін в компіляторі мови Ді версії 1.0 - www.digitalmars.com/d/1.0/changelog.html. Читальний - www.webcitation.org/686MJlXPQ з першоджерела 1 червня 2012.
  2. Список змін в компіляторі мови Ді версії 2.0 - dlang.org / changelog.html. Читальний - www.webcitation.org/686MKPo4M з першоджерела 1 червня 2012.
  3. DMD 1.00 - here it is! (Digitalmars.D.announce) - (Англ.)
  4. D 2.0 changelog - www.digitalmars.com/d/2.0/changelog.html. Читальний - www.webcitation.org/686MLQFHl з першоджерела 1 червня 2012. (Англ.)
  5. Історичний момент включення вихідних текстів у dmd 1.041 - www.digitalmars.com/d/1.0/changelog.html # new1_041 (Англ.)
  6. D x86 Inline Assembler - www.digitalmars.com/d/1.0/iasm.html Вбудований асемблер в D
  7. [1] - www.digitalmars.com / d / index.html Приклад програми на D (Англ.)
  8. [2] - www.digitalmars.com/d/1.0/unittest.html Модульне тестування D програми (Англ.)
  9. [3] - www.digitalmars.com/d/1.0/dbc.html Контрактне програмування в D (Англ.)
  10. Embedded Documentation - www.digitalmars.com/d/1.0/ddoc.html Вбудований генератор документації мови D (Англ.)
  11. [4] - www.digitalmars.com/d/1.0/html.html dmd може копміліровать програми з html файлу (Англ.)
  12. Named Return Value Optimization - www.digitalmars.com/d/1.0/glossary.html # nrvo Особиста техніка оптимізації Уолтера Брайта (Англ.)
  13. The C Preprocessor Versus D - www.digitalmars.com/d/1.0/pretod.html - digitalmars.com (Англ.)
  14. Conditional Compilation - www.digitalmars.com/d/1.0/version.html - digitalmars.com (Англ.)
  15. std.bitmanip - www.digitalmars.com/d/2.0/phobos/std_bitmanip.html (Англ.)
  16. [5] - www.digitalmars.com/d/1.0/class.html # StaticConstructor Статичні конструктори в D
  17. [Яик програмування C. Друге видання. Вступ: Як і у будь-яких мов, у C є свої недоліки: деякі операції мають нелогічний пріоритет <...>]