Глава 7 готова

This commit is contained in:
Alexander Zhirov 2023-02-28 10:59:52 +03:00
parent 428eaab881
commit f10bcce1e4
1 changed files with 65 additions and 1 deletions

View File

@ -27,7 +27,7 @@
- [7.3.1. Перечисляемые типы](#7-3-1-перечисляемые-типы)
- [7.3.2. Свойства перечисляемых типов](#7-3-2-свойства-перечисляемых-типов)
- [7.4. alias](#7-4-alias)
- [7.5. Параметризированные контексты (конструкция template)](#75-параметризированные-контексты-конструкция-template)
- [7.5. Параметризированные контексты (конструкция template)](#7-5-параметризированные-контексты-конструкция-template)
- [7.5.1. Одноименные шаблоны](#7-5-1-одноименные-шаблоны)
- [7.5.2. Параметр шаблона this](#7-5-2-параметр-шаблона-this-4)
- [7.6. Инъекции кода с помощью конструкции mixin template](#7-6-инъекции-кода-с-помощью-конструкции-mixin-template)
@ -128,6 +128,8 @@ unittest
В отличие от имен `c1` и `с2`, допускающих привязку к любому объекту, имена `s1` и `s2` прочно привязаны к реальным объектам. Нет способа заставить два имени ссылаться на один и тот же объект-структуру (кроме ключевого слова `alias`, задающего простую эквивалентность имен; см. раздел 7.4), и не бывает имени структуры без закрепленного за ним значения, так что сравнение `s1 is null` бессмысленно и порождает ошибку во время компиляции.
[В начало ⮍](#7-1-1-семантика-копирования) [Наверх ⮍](#7-другие-пользовательские-типы)
### 7.1.2. Передача объекта-структуры в функцию
Поскольку объект типа `struct` ведет себя как значение, он и передается в функцию по значению.
@ -157,6 +159,8 @@ void fun(ref S s) // fun получает ссылку
Раз уж мы заговорили о `ref`, отметим, что `this` передается по ссылке внутрь методов структуры `S` в виде скрытого параметра `ref S`.
[В начало ⮍](#7-1-2-передача-объекта-структуры-в-функцию) [Наверх ⮍](#7-другие-пользовательские-типы)
### 7.1.3. Жизненный цикл объекта-структуры
В отличие от объектов-классов, объектам-структурам не свойственно бесконечное время жизни (lifetime). Время жизни для них четко ограничено так же как для временных (стековых) объектов функций. Чтобы создать объект-структуру, задайте имя нужного типа, как если бы вы вызывали функцию:
@ -194,6 +198,8 @@ unittest
Поначалу может раздражать разница в синтаксисе выражения, создающего объект-структуру `Test(‹аргументы›)`, и выражения, создающего объект-класс `new Test(‹аргументы›)`. D мог бы отказаться от использования ключевого слова new при создании объектов-классов, но это `new` напоминает программисту, что выполняется операция выделения памяти (то есть необычное действие).
[В начало ⮍](#7-1-3-жизненный-цикл-объекта-структуры) [Наверх ⮍](#7-другие-пользовательские-типы)
#### 7.1.3.1. Конструкторы
Конструктор структуры определяется так же, как конструктор класса (см. раздел 6.3.1):
@ -240,6 +246,8 @@ struct Test
Зачем нужно такое ограничение? Все из-за `T.init` значения по умолчанию, определяемого каждым типом. Оно должно быть статически известно, что противоречит существованию конструктора по умолчанию, выполняющего произвольный код. (Для классов `T.init` это пустая ссылка `null`, а не объект, построенный по умолчанию.) Правило для всех структур: конструктор по умолчанию инициализирует все поля объекта-структуры значениями по умолчанию.
[В начало ⮍](#7-1-3-1-конструкторы) [Наверх ⮍](#7-другие-пользовательские-типы)
#### 7.1.3.2. Делегирование конструкторов
Скопируем пример из раздела 6.3.2 с заменой ключевого слова `class` на `struct`:
@ -263,6 +271,8 @@ struct Widget
Код запускается, не требуя внесения каких-либо других изменений. Так же как и классы, структуры позволяют одному конструктору делегировать построение объекта другому конструктору с теми же ограничениями.
[В начало ⮍](#7-1-3-2-делегирование-конструкторов) [Наверх ⮍](#7-другие-пользовательские-типы)
#### 7.1.3.3. Алгоритм построения
Классу приходится заботиться о выделении динамической памяти и инициализации своего базового подобъекта (см. раздел 6.3.3). Со структурами все гораздо проще, поскольку выделение памяти явный шаг алгоритма построения. Алгоритм построения объекта-структуры типа `T` по шагам:
@ -272,6 +282,8 @@ struct Widget
Если инициализация некоторых или всех полей структуры выглядит как `= void`, объем работ на первом шаге можно сократить, хотя и редко намного, зато такой маневр часто порождает трудноуловимые ошибки в вашем коде (тем не менее случай оправданного применения сокращенной инициализации иллюстрирует пример с классом `Transmogrifier` в разделе 6.3.3).
[В начало ⮍](#7-1-3-3-алгоритм-построения) [Наверх ⮍](#7-другие-пользовательские-типы)
#### 7.1.3.4. Конструктор копирования this(this)
Предположим, требуется определить объект, который содержит локальный (`private`) массив и предоставляет ограниченный API для манипуляции этим массивом:
@ -412,6 +424,8 @@ unittest
Вкратце, если вы определите для некоторой структуры конструктор копирования `this(this)`, компилятор позаботится о том, чтобы конструктор копирования вызывался в каждом случае копирования этого объекта-структуры независимо от того, является ли он самостоятельным объектом или частью более крупного объекта-структуры.
[В начало ⮍](#7-1-3-4-конструктор-копирования-this-this) [Наверх ⮍](#7-другие-пользовательские-типы)
#### 7.1.3.5. Аргументы в пользу this(this)
Зачем был введен конструктор копирования? Ведь ничего подобного в других языках пока нет. Почему бы просто не передавать исходный объект в будущую копию (как это делает C++)?
@ -510,6 +524,8 @@ unittest
Вызов функции `move` гарантирует, что `w` будет перемещена, а ее содержимое будет заменено пустым, сконструированным по умолчанию объектом типа `Widget`. Кстати, это один из тех случаев, где пригодится неизменяемый и не порождающий исключения конструктор по умолчанию `Widget.init` (см. раздел 7.1.3.1). Без него сложно было бы найти способ оставить источник перемещения в строго определенном пустом состоянии.
[В начало ⮍](#7-1-3-5-аргументы-в-пользу-this-this) [Наверх ⮍](#7-другие-пользовательские-типы)
#### 7.1.3.6. Уничтожение объекта и освобождение памяти
Структура может определять деструктор с именем `~this()`:
@ -559,6 +575,8 @@ void main()
Освобождение памяти объекта-структуры по идее выполняется сразу же после деструкции.
[В начало ⮍](#7-1-3-6-уничтожение-объекта-и-освобождение-памяти) [Наверх ⮍](#7-другие-пользовательские-типы)
#### 7.1.3.7. Алгоритм уничтожения структуры
По умолчанию объекты-структуры уничтожаются в порядке, строго обратном порядку их создания. То есть первым уничтожается объект-структура, определенный в заданной области видимости последним:
@ -610,6 +628,8 @@ void main()
Можно явно инициировать вызов деструктора объекта-структуры с помощью инструкции `clear(объект);`. С функцией `clear` мы уже познакомились в разделе 6.3.5. Тогда она оказалась полезной для уничтожения состояния объекта-класса. Для объектов-структур функция `clear` делает то же самое: вызывает деструктор, а затем копирует биты значения `.init` в область памяти объекта. В результате получается правильно сконструированный объект, правда, без какого-либо интересного содержания.
[В начало ⮍](#7-1-3-7-алгоритм-уничтожения-структуры) [Наверх ⮍](#7-другие-пользовательские-типы)
### 7.1.4. Статические конструкторы и деструкторы
Структура может определять любое число статических конструкторов и деструкторов. Это средство полностью идентично одноименному средству для классов, с которым мы уже встречались в разделе 6.3.6.
@ -658,6 +678,8 @@ void main()
Порядок выполнения очевиден для статических конструкторов и деструкторов, расположенных внутри одного модуля, но в случае нескольких модулей не всегда все так же ясно. Порядок выполнения статических конструкторов и деструкторов из разных модулей определен в разделе 6.3.6.
[В начало ⮍](#7-1-4-статические-конструкторы-и-деструкторы) [Наверх ⮍](#7-другие-пользовательские-типы)
### 7.1.5. Методы
Структуры могут определять функции-члены, также называемые методами. Поскольку в случае структур о наследовании и переопределении речи нет, методы структур лишь немногим больше, чем функции.
@ -702,6 +724,8 @@ struct S
Некоторые особые методы заслуживают более тщательного рассмотрения. К ним относятся оператор присваивания `opAssign`, используемый оператором `=`, оператор равенства `opEquals`, используемый операторами `==` и `!=`, а также упорядочивающий оператор `opCmp`, используемый операторами `<`, `<=`, `>=` и `>`. На самом деле, эта тема относится к главе 12, так как затрагивает вопрос перегрузки операторов, но эти операторы особенные: компилятор может сгенерировать их автоматически, со всем их особым поведением.
[В начало ⮍](#7-1-5-методы) [Наверх ⮍](#7-другие-пользовательские-типы)
#### 7.1.5.1. Оператор присваивания
По умолчанию, если задать:
@ -785,6 +809,8 @@ ref Widget opAssign(ref Widget rhs)
}
```
[В начало ⮍](#7-1-5-1-оператор-присваивания) [Наверх ⮍](#7-другие-пользовательские-типы)
#### 7.1.5.2. Сравнение структур на равенство
Средство для сравнения объектов-структур предоставляется «в комплекте» это операторы `==` и `!=`. Сравнение представляет собой поочередное сравнение внутренних элементов объектов и возвращает `false`, если хотя бы два соответствующих друг другу элемента сравниваемых объектов не равны, иначе результатом сравнения является `true`.
@ -867,6 +893,8 @@ a.leftBottom.opEquals(b.leftBottom) && a.rightTop.opEquals(b.rightTop)
Этот пример также показывает, что сравнение выполняется в порядке объявления полей (т. е. поле `leftBottom` проверяется до проверки `rightTop`), и если встретились два неравных поля, сравнение завершается до того, как будут проверены все поля, благодаря сокращенному вычислению логических связок, построенных с помощью оператора `&&` (short circuit evaluation).
[В начало ⮍](#7-1-5-2-сравнение-структур-на-равенство) [Наверх ⮍](#7-другие-пользовательские-типы)
### 7.1.6. Статические внутренние элементы
Структура может определять статические данные и статические внутренние функции. Помимо ограниченной видимости и подчинения правилам доступа (см. раздел 7.1.7) режим работы статических внутренних функций ничем не отличается от режима работы обычных функций. Нет скрытого параметра `this`, не вовлечены никакие другие особые механизмы.
@ -913,6 +941,8 @@ void main()
[5, 3]
```
[В начало ⮍](#7-1-6-статические-внутренние-элементы) [Наверх ⮍](#7-другие-пользовательские-типы)
### 7.1.7. Спецификаторы доступа
Структуры подчиняются спецификаторам доступа `private` (см. раздел 6.7.1), `package` (см. раздел 6.7.2), `public` (см. раздел 6.7.4) и `export` (см. раздел 6.7.5) тем же образом, что и классы. Спецификатор `protected` применительно к структурам не имеет смысла, поскольку структуры не поддерживают наследование.
@ -931,6 +961,8 @@ struct S
Заметим, что хотя ключевое слово `export` разрешено везде, где синтаксис допускает применение спецификатора доступа, семантика этого ключевого слова зависит от реализации.
[В начало ⮍](#7-1-7-спецификаторы-доступа) [Наверх ⮍](#7-другие-пользовательские-типы)
### 7.1.8. Вложенность структур и классов
Часто бывает удобно вложить в структуру другую структуру или класс. Например, контейнер дерева можно представить как оболочку-структуру с простым интерфейсом поиска, а внутри нее для определения узлов дерева использовать полиморфизм.
@ -999,6 +1031,8 @@ class Window
В отличие от классов, вложенных в другие классы, вложенные структуры и классы, вложенные в другие структуры, не обладают никаким скрытым внутренним элементом `outer` никакой специальный код не генерируется. Такие вложенные типы определяются в основном со структурной целью чтобы получить нужное управление доступом.
[В начало ⮍](#7-1-8-вложенность-структур-и-классов) [Наверх ⮍](#7-другие-пользовательские-типы)
### 7.1.9. Структуры, вложенные в функции
Вспомним, что говорилось в разделе 6.11.1: вложенные классы находятся в привилегированном положении, ведь они обладают особыми, уникальными свойствами. Вложенному классу доступны параметры и локальные переменные включающей функции. Если вы возвращаете вложенный класс в качестве результата функции, компилятор даже размещает кадр функции в динамической памяти, чтобы параметры и локальные переменные функции выжили после того, как она вернет управление.
@ -1035,6 +1069,8 @@ unittest
Вложенные структуры практически бесполезны, разве что, по сравнению со вложенными классами, позволяют избежать беспричинного ограничения. Функции не могут возвращать объекты вложенных структур, так как вызывающему их коду недоступна информация о типах таких объектов. Используя замысловатые вложенные структуры, код неявно побуждает создавать все больше сложных функций, а в идеале именно этого надо избегать в первую очередь.
[В начало ⮍](#7-1-9-структуры-вложенные-в-функции) [Наверх ⮍](#7-другие-пользовательские-типы)
### 7.1.10. Порождение подтипов в случае структур. Атрибут @disable
К структурам неприменимы наследование и полиморфизм, но этот тип данных по-прежнему поддерживает конструкцию `alias this`, впервые представленную в разделе 6.13. С помощью `alias this` можно сделать структуру подтипом любого другого типа. Определим, к примеру, простой тип `Final`, поведением очень напоминающий ссылку на класс во всем, кроме того что переменную типа `Final` невозможно перепривязать! Пример использования переменной `Final`:
@ -1174,6 +1210,8 @@ struct Final(T)
Соблюдение такого соглашения сводит к минимуму риск непредвиденных коллизий. (Конечно, иногда можно намеренно перехватывать некоторые методы, оставив вызовы к ним за перехватчиком.)
[В начало ⮍](#7-1-10-порождение-подтипов-в-случае-структур-атрибут-disable) [Наверх ⮍](#7-другие-пользовательские-типы)
#### 7.1.11. Взаимное расположение полей. Выравнивание
Как располагаются поля в объекте-структуре? D очень консервативен в отношении структур: он располагает элементы их содержимого в том же порядке, в каком они указаны в определении структуры, но сохраняет за собой право вставлять между полями *отступы* (*padding*). Рассмотрим пример:
@ -1238,6 +1276,8 @@ void main()
D гарантирует, что все байты отступов последовательно заполняются нулями.
[В начало ⮍](#7-1-11-взаимное-расположение-полей-выравнивание) [Наверх ⮍](#7-другие-пользовательские-типы)
#### 7.1.11.1. Атрибут align
Чтобы перекрыть выбор компилятора, определив собственное выравнивание, что повлияет на вставляемые отступы, объявляйте поля с атрибутом `align`. Такое переопределение может понадобиться для взаимодействия с определенной аппаратурой или для работы по бинарному протоколу, задающему особое выравнивание. Пример атрибута `align` в действии:
@ -1278,6 +1318,8 @@ struct Node
Если этот код выполнит присваивание `объект.next = new Node` (то есть заполнит `объект.next` ссылкой, контролируемой сборщиком мусора), хаос обеспечен: неверно выровненная ссылка пропадает из поля зрения сборщика мусора, память может быть освобождена, и `объект.next` превращается в «висячий» указатель.
[В начало ⮍](#7-1-11-1-атрибут-align) [Наверх ⮍](#7-другие-пользовательские-типы)
## 7.2. Объединение
Объединения в стиле C можно использовать и в D, но не забывайте, что делать это нужно редко и с особой осторожностью.
@ -1386,6 +1428,8 @@ unittest
В конце концов вы должны понять, что объединение не такое уж зло, каким может показаться. Как правило, использовать объединение вместо того, чтобы играть типами с помощью выражения `cast`, хороший тон в общении между программистом и компилятором. Объединение указателя и целого числа указывает сборщику мусора, что ему следует быть осторожнее и не собирать этот указатель. Если вы сохраните указатель в целом числе и будете время от времени преобразовывать его назад к типу указателя (с помощью `cast`), результаты окажутся непредсказуемыми, ведь сборщик мусора может забрать память, ассоциированную с этим тайным указателем.
[В начало ⮍](#7-2-объединение) [Наверх ⮍](#7-другие-пользовательские-типы)
## 7.3. Перечисляемые значения
Типы, принимающие всего несколько определенных значений, оказались очень полезными настолько полезными, что язык Java после нескольких лет героических попыток эмулировать перечисляемые типы с помощью идиомы в конце концов добавил их к основным типам. Определить хорошие перечисляемые типы непросто в C (и особенно в C++) типу `enum` присущи свои странности. D попытался учесть предшествующий опыт, определив простое и полезное средство для работы с перечисляемыми типами.
@ -1444,6 +1488,8 @@ enum
Когда бы вы ни использовали, например, идентификатор `green`, код будет вести себя так, будто вместо этого идентификатора вы написали `Color(0, 255, 0)`.
[В начало ⮍](#7-3-перечисляемые-значения) [Наверх ⮍](#7-другие-пользовательские-типы)
### 7.3.1. Перечисляемые типы
Можно дать имя группе перечисляемых значений, создав таким образом новый тип на ее основе:
@ -1487,6 +1533,8 @@ assert(F.a == F.d); // Тест пройден
Корень этой проблемы в том, что наибольшее значение типа `int`, которое может быть представлено значением типа `float`, равно `16_777_216`, и выход за эту границу сопровождается все возрастающими диапазонами целых значений, представляемых одним и тем же числом типа `float`.
[В начало ⮍](#7-3-1-перечисляемые-типы) [Наверх ⮍](#7-другие-пользовательские-типы)
### 7.3.2. Свойства перечисляемых типов
Для всякого перечисляемого типа `E` определены три свойства: `E.init` (это свойство принимает первое из значений, определенных в `E`), `E.min` (наименьшее из определенных в `E` значений) и `E.max` (наибольшее из определенных в `E` значений). Два последних значения определены, только если базовым типом `E` является тип, поддерживающий сравнение во время компиляции с помощью оператора `<`.
@ -1518,6 +1566,8 @@ void main()
Рассмотренная функция `toString` уже реализована в модуле `std.conv` стандартной библиотеки, имеющем дело с общими преобразованиями. Имя этой функции немного отличается от того, что использовали мы: вам придется писать `to!string(w)` вместо `toString(w)`, что говорит о гибкости этой функции (также можно сделать вызов `to!dstring(w)` или `to!byte(w)` и т. д.). Этот же модуль определяет и обратную функцию, которая конвертирует строку в значение перечисляемого типа; например вызов `to!OddWord("acini")` возвращает `OddWord.acini`.
[В начало ⮍](#7-3-2-свойства-перечисляемых-типов) [Наверх ⮍](#7-другие-пользовательские-типы)
## 7.4. alias
В ряде случаев мы уже имели дело с `size_t` целым типом без знака, достаточно вместительным, чтобы представить размер любого объекта. Тип `size_t` не определен языком, он просто принимает форму `uint` или `ulong` в зависимости от адресного пространства конечной системы (32 или 64 бита соответственно).
@ -1612,6 +1662,8 @@ else
С помощью объявления псевдоним `ptrdiff_t` привязывается к разным типам в зависимости от того, по какой ветке статического условия пойдет поток управления. Без этой возможности привязки код, которому потребовался такой тип, пришлось бы разместить в одной из веток `static if`.
[В начало ⮍](#7-4-alias) [Наверх ⮍](#7-другие-пользовательские-типы)
## 7.5. Параметризированные контексты (конструкция template)
Мы уже рассмотрели средства, облегчающие параметризацию во время компиляции (эти средства сродни шаблонам из C++ и родовым типам из языков Java и C#), это функции (см. раздел 5.3), параметризированные классы (см. раздел 6.14) и параметризированные структуры, которые подчиняются тем же правилам, что и параметризированные классы. Тем не менее иногда во время компиляции требуется каким-либо образом манипулировать типами, не определяя функцию, структуру или класс. Один из механизмов, подходящих под это описание (широко используемый в C++), выбор того или иного типа в зависимости от статически известного логического условия. При этом не определяется никакой новый тип и не вызывается никакая функция лишь создается псевдоним для одного из существующих типов.
@ -1710,6 +1762,8 @@ template factorial(uint n)
Несмотря на то что `factorial` является совершенным функциональным определением, в данном случае это не лучший подход. При необходимости вычислять значения во время компиляции, пожалуй, стоило бы воспользоваться механизмом вычислений во время компиляции (см. раздел 5.12). В отличие от приведенного выше шаблона `factorial`, функция `factorial` более гибка, поскольку может вычисляться как во время компиляции, так и во время исполнения. Конструкция `template` больше всего подходит для манипуляции типами, имеющей место в `Select` и `isSomeString`.
[В начало ⮍](#7-5-параметризированные-контексты-конструкция-template) [Наверх ⮍](#7-другие-пользовательские-типы)
### 7.5.1. Одноименные шаблоны
Конструкция `template` может определять любое количество идентификаторов, но, как видно из предыдущих примеров, нередко в ней определен ровно один идентификатор. Обычно шаблон определяется лишь с целью решить единственную задачу и в качестве результата сделать доступным единственный идентификатор (такой как `Type` в случае `Select` или `value` в случае `isSomeString`).
@ -1751,6 +1805,8 @@ unittest
Это сообщение об ошибке вызвано соблюдением правила об одноименности: перед тем как делать что-либо еще, компилятор расширяет вызов `isNumeric!(int)` до `isNumeric!(int).isNumeric`. Затем пользовательский код делает попытку заполучить значение `isNumeric!(int).isNumeric.test1`, что равносильно попытке получить внутренний элемент `test1` из логического значения, отсюда и сообщение об ошибке. Короче говоря, используйте одноименные шаблоны тогда и только тогда, когда хотите открыть доступ лишь к одному идентификатору. Этот случай скорее частый, чем редкий, поэтому одноименные шаблоны очень популярны и удобны.
[В начало ⮍](#7-5-1-одноименные-шаблоны) [Наверх ⮍](#7-другие-пользовательские-типы)
### 7.5.2. Параметр шаблона this[^4]
Познакомившись с классами и структурами, можно параметризовать наш обобщенный метод типом неявного аргумента `this`. Например:
@ -1780,6 +1836,8 @@ unittest
Также параметр `this` удобен в случае, когда один метод нужно использовать для разных квалификаторов неизменяемости объекта (см. главу 8).
[В начало ⮍](#7-5-2-параметр-шаблона-this-4) [Наверх ⮍](#7-другие-пользовательские-типы)
## 7.6. Инъекции кода с помощью конструкции mixin template
При некоторых программных решениях приходится добавлять шаблонный код (такой как определения данных и методов) в одну или несколько реализаций классов. К типичным примерам относятся поддержка сериализации, шаблон проектирования «Наблюдатель» и передача событий в оконных системах.
@ -1867,6 +1925,8 @@ assert(MyDouble.getX() == 5.5);
Таким образом, шаблоны `mixin` это *почти* как копирование и вставка; вы можете многократно копировать и вставлять код, а потом указывать, к какой именно вставке хотите обратиться.
[В начало ⮍](#7-6-инъекции-кода-с-помощью-конструкции-mixin-template) [Наверх ⮍](#7-другие-пользовательские-типы)
### 7.6.1. Поиск идентификаторов внутри mixin
Самая большая разница между шаблоном `mixin` и обычным шаблоном (в том виде, как он определен в разделе 7.5), способная вызвать больше всего вопросов, это поиск имен.
@ -1911,6 +1971,8 @@ void main()
Склонность шаблонов `mixin` привязываться к локальным идентификаторам придает им выразительности, но следовать их логике становится сложно. Такое поведение делает шаблоны `mixin` применимыми лишь в ограниченном количестве случаев; прежде чем доставать из ящика с инструментами эти особенные ножницы, необходимо семь раз отмерить.
[В начало ⮍](#7-6-1-поиск-идентификаторов-внутри-mixin) [Наверх ⮍](#7-другие-пользовательские-типы)
## 7.7. Итоги
Классы позволяют эффективно представить далеко не любую абстракцию. Например, они не подходят для мелкокалиберных объектов, контекстно-зависимых ресурсов и типов значений. Этот пробел восполняют структуры. В частности, благодаря конструкторам и деструкторам легко определять типы контекстно-зависимых ресурсов.
@ -1925,6 +1987,8 @@ void main()
Кроме того, предлагаются параметризированные контексты, принимающие форму шаблонов `mixin`, которые во многом ведут себя подобно макросам. В будущем шаблоны `mixin` может заменить развитое средство AST-макросов.
[В начало ⮍](#7-7-итоги) [Наверх ⮍](#7-другие-пользовательские-типы)
[^1]: Не считая эквивалентных имен, создаваемых с помощью `alias`, о чем мы еще поговорим в этой главе (см. раздел 7.4).
[^2]: Термин «клуктура» предложил Бартош Милевски.
[^3]: Кроме того, `код1` может сохранить указатель на значение `w`, которое использует `код2`.