LEARN X · ЗА 16 МИН

D

Экспресс-тур по языку D: типы, строки, циклы, массивы, ranges, шаблоны, классы и структуры, CTFE и контракты — весь язык на одной странице.

D — компилируемый системный язык со сборщиком мусора, который читается как Python, а работает как C++. Ниже весь язык за 16 минут: только код и комментарии.

Структура программы

Точка входа — main. Вывод — через модуль std.stdio.

// Однострочный комментарий
/* Блочный комментарий */
/+ Вложенный комментарий /+ можно внутри +/ +/

import std.stdio; // подключаем модуль ввода-вывода

// main может возвращать void или int
void main()
{
    writeln("Привет, D!");   // writeln добавляет перевод строки
    write("без перевода\n"); // write — без него
    writefln("%d + %d = %d", 2, 3, 2 + 3); // форматированный вывод
}

Переменные и типы

auto выводит тип, immutable и const запрещают изменение.

int    i = 42;        // целое (32 бита)
long   big = 9_000;   // 64 бита; _ — разделитель разрядов
double d = 3.14;      // число с плавающей точкой
bool   flag = true;   // true / false
char   c = 'A';       // один UTF-8 кодовый блок

auto x = 10;          // тип выведен: int
auto y = 2.5;         // тип выведен: double

immutable pi = 3.14159; // нельзя изменить никогда
const    n  = 100;      // нельзя изменить через эту ссылку

// Все типы имеют значение по умолчанию (.init)
int zero;             // = 0
double nan;           // = NaN (не 0.0!)

int.max.writeln;      // 2147483647 — свойства типов
double.epsilon.writeln;

Строки

string — это immutable(char)[], то есть неизменяемый срез байтов UTF-8.

import std.string, std.uni, std.conv;

string s = "Hello";
string multi = `сырая строка: \n не экранируется`; // обратные кавычки
string wysiwyg = r"C:\path\file"; // r-строка тоже сырая

writeln(s.length);          // длина в БАЙТАХ
writeln(s ~ " мир");        // ~ — конкатенация
writeln(s.toUpper);         // HELLO
writeln("  тримминг  ".strip);
writeln("a,b,c".split(",")); // ["a", "b", "c"]
writeln("42".to!int + 1);    // 43 — строка -> число

// Интерполяция через format
import std.format;
writeln(format("x = %d, y = %.2f", 10, 2.5));

Операторы и условия

final switch требует перечислить все случаи (проверка на этапе компиляции).

int a = 7;

if (a > 10)
    writeln("много");
else if (a > 5)
    writeln("средне");
else
    writeln("мало");

// Тернарный оператор
auto sign = a >= 0 ? "плюс" : "минус";

// switch умеет строки и диапазоны
switch (a)
{
    case 1: .. case 5:        // диапазон 1..5
        writeln("1-5");
        break;
    case 7:
        writeln("семь");
        break;
    default:
        writeln("другое");
}

// final switch — компилятор требует все enum-значения
enum Color { red, green, blue }
Color col = Color.green;
final switch (col)
{
    case Color.red:   writeln("красный");   break;
    case Color.green: writeln("зелёный");   break;
    case Color.blue:  writeln("синий");     break;
}

Циклы

foreach с диапазоном a..b — идиоматичный способ перебора чисел.

// Классический for
for (int k = 0; k < 3; k++)
    write(k, " ");        // 0 1 2
writeln;

// foreach по диапазону: 0,1,2,3,4
foreach (k; 0 .. 5)
    write(k, " ");
writeln;

// foreach по массиву с индексом
auto arr = [10, 20, 30];
foreach (idx, val; arr)
    writeln(idx, ": ", val);

// foreach_reverse — в обратном порядке
foreach_reverse (val; arr)
    write(val, " ");      // 30 20 10
writeln;

// while и do-while
int n = 3;
while (n > 0) { write(n, " "); n--; }   // 3 2 1
do { writeln("хотя бы раз"); } while (false);

Массивы и срезы

Срезы (slices) — это вид на участок памяти: указатель + длина, без копирования.

int[] dyn = [1, 2, 3];   // динамический массив
dyn ~= 4;                // добавить элемент
dyn ~= [5, 6];           // добавить массив
writeln(dyn);            // [1, 2, 3, 4, 5, 6]
writeln(dyn.length);     // 6

int[3] fixed = [1, 2, 3]; // массив фиксированной длины (на стеке)

// Срезы: arr[начало .. конец] — конец не включается
writeln(dyn[1 .. 4]);    // [2, 3, 4]
writeln(dyn[$ - 2 .. $]); // [5, 6] — $ это длина

// Срезы разделяют память!
auto part = dyn[0 .. 2];
part[0] = 99;            // меняет и dyn[0]

// Ассоциативные массивы (хеш-карты)
int[string] ages;
ages["Аня"] = 25;
ages["Боб"] = 30;
writeln(ages["Аня"]);    // 25
writeln("Аня" in ages);  // указатель != null
foreach (name, age; ages)
    writeln(name, " -> ", age);

Функции

UFCS: x.f(y) эквивалентно f(x, y) — любую функцию можно звать как метод.

// Обычная функция
int add(int a, int b) { return a + b; }

// Значения по умолчанию
int inc(int x, int step = 1) { return x + step; }

// Несколько возвратов через кортеж (auto + tuple)
import std.typecons : tuple;
auto minMax(int[] xs)
{
    return tuple(xs[0], xs[$ - 1]);
}

// UFCS: square(5) можно записать как 5.square
int square(int n) { return n * n; }

void demo()
{
    writeln(add(2, 3));   // 5
    writeln(inc(10));     // 11 (step по умолчанию)
    writeln(5.square);    // 25 — UFCS
    writeln(5.square.inc); // 26 — цепочка
}

// Лямбды
auto twice = (int n) => n * 2;
writeln(twice(21));       // 42

Структуры и классы

struct — значимый тип (копируется, на стеке). class — ссылочный тип (на куче, наследование, GC).

// struct: копируется по значению, нет наследования
struct Point
{
    int x, y;
    int sum() { return x + y; } // метод
}

Point p1 = Point(1, 2);
Point p2 = p1;          // КОПИЯ
p2.x = 99;              // p1.x не изменился
writeln(p1.sum());      // 3

// class: ссылочный тип, наследование, полиморфизм
class Animal
{
    string name;
    this(string name) { this.name = name; } // конструктор
    string speak() { return "..."; }
}

class Dog : Animal       // наследование через :
{
    this(string name) { super(name); }
    override string speak() { return "Гав!"; }
}

Animal a = new Dog("Рекс"); // ссылка на куче
writeln(a.name, ": ", a.speak()); // Рекс: Гав! (полиморфизм)

Ranges и алгоритмы

Ranges — основа стандартной библиотеки. std.algorithm работает с любым range лениво.

import std.algorithm; // map, filter, reduce, sort, each
import std.range;     // iota, take, chain
import std.array;     // array — материализовать range

auto nums = [1, 2, 3, 4, 5, 6];

// map: преобразовать каждый элемент
auto sq = nums.map!(n => n * n);
writeln(sq);                  // [1, 4, 9, 16, 25, 36]

// filter: оставить подходящие
auto even = nums.filter!(n => n % 2 == 0);
writeln(even);                // [2, 4, 6]

// reduce / fold: свернуть в одно значение
auto total = nums.reduce!((acc, n) => acc + n);
writeln(total);               // 21

// Цепочки ленивы — вычисляются по требованию
auto result = nums
    .filter!(n => n % 2 == 0)
    .map!(n => n * 10)
    .array;                   // .array — собрать в массив
writeln(result);              // [20, 40, 60]

// iota — ленивый диапазон чисел
writeln(iota(1, 6).sum);      // 15 (1+2+3+4+5)
writeln(iota(0, 100).take(3)); // [0, 1, 2]

Шаблоны

Шаблоны параметризуются типами и значениями. Аргументы шаблона — в скобках после !.

// Шаблонная функция: T выводится автоматически
T max2(T)(T a, T b)
{
    return a > b ? a : b;
}

writeln(max2(3, 7));        // 7   — T = int
writeln(max2(2.5, 1.0));   // 2.5 — T = double
writeln(max2!string("a", "b")); // явно T = string

// Шаблонная структура (как generic-контейнер)
struct Box(T)
{
    T value;
    T get() { return value; }
}

auto bi = Box!int(42);
auto bs = Box!string("hi");
writeln(bi.get(), " ", bs.get());

// Ограничения шаблона: только для числовых типов
import std.traits : isNumeric;
T doubleIt(T)(T x) if (isNumeric!T)
{
    return x * 2;
}
writeln(doubleIt(21));     // 42

Управление памятью

По умолчанию работает сборщик мусора (GC). scope(exit) гарантирует очистку при выходе из блока.

import std.stdio;

void memory()
{
    // new выделяет память в куче под управлением GC
    auto data = new int[1000]; // GC освободит сам

    // scope(exit) — выполнится при выходе из области видимости
    scope(exit)    writeln("всегда при выходе");
    scope(success) writeln("если без исключения");
    scope(failure) writeln("если было исключение");

    // Ручное управление: malloc/free из core.stdc.stdlib
    // для кода без GC помечают функцию как @nogc
}

// @nogc — компилятор запрещает любые GC-аллокации внутри
@nogc int pureAdd(int a, int b)
{
    return a + b; // никаких new, ~, замыканий
}

// @safe / @trusted / @system — уровни безопасности памяти
@safe int safeFn() { return 1; } // запрещены опасные операции

Особенности: CTFE и контракты

CTFE — выполнение функций на этапе компиляции. Контракты (in/out/invariant) проверяют условия.

// CTFE: обычная функция считается в compile-time
int factorial(int n)
{
    return n <= 1 ? 1 : n * factorial(n - 1);
}

enum fact5 = factorial(5);    // ВЫЧИСЛЕНО при компиляции = 120
static immutable table = factorial(6); // тоже compile-time

// static if — ветвление на этапе компиляции
void info(T)()
{
    static if (is(T == int))
        writeln("это int");
    else
        writeln("другой тип");
}

// Контракты функции: in (предусловие), out (постусловие)
int sqrtInt(int x)
in  { assert(x >= 0, "нужно неотрицательное"); }
out (result) { assert(result * result <= x); }
do {
    import std.math : sqrt;
    return cast(int) sqrt(cast(double) x);
}

// Инвариант структуры — проверяется после каждого метода
struct Account
{
    int balance;
    invariant { assert(balance >= 0, "баланс не может быть отрицательным"); }
}

// unittest — встроенные тесты, запускаются флагом -unittest
unittest
{
    assert(factorial(5) == 120);
    assert(sqrtInt(9) == 3);
}
Поддержать проект