From 0cad05de5aa7d491b0a79eb660810a1062232ed5 Mon Sep 17 00:00:00 2001 From: Alexander Zhirov Date: Tue, 28 Feb 2023 14:04:55 +0300 Subject: [PATCH] =?UTF-8?q?=D0=93=D0=BB=D0=B0=D0=B2=D0=B0=208=20=D0=B3?= =?UTF-8?q?=D0=BE=D1=82=D0=BE=D0=B2=D0=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- 08-квалификаторы-типа/README.md | 493 +++++++++++++++++++++ 08-квалификаторы-типа/images/image-8-6.png | Bin 0 -> 11550 bytes 2 files changed, 493 insertions(+) create mode 100644 08-квалификаторы-типа/images/image-8-6.png diff --git a/08-квалификаторы-типа/README.md b/08-квалификаторы-типа/README.md index 407e8fc..0513c3e 100644 --- a/08-квалификаторы-типа/README.md +++ b/08-квалификаторы-типа/README.md @@ -1,5 +1,16 @@ # 8. Квалификаторы типа +- [8.1. Квалификатор immutable](#8-1-квалификатор-immutable) + - [8.1.1. Транзитивность](#8-1-1-транзитивность) +- [8.2. Составление типов с помощью immutable](#8-2-составление-типов-с-помощью-immutable) +- [8.3. Неизменяемые параметры и методы](#8-3-неизменяемые-параметры-и-методы) +- [8.4. Неизменяемые конструкторы](#8-4-неизменяемые-конструкторы) +- [8.5. Преобразования с участием immutable](#8-5-преобразования-с-участием-immutable) +- [8.6. Квалификатор const](#8-6-квалификатор-const) +- [8.7. Взаимодействие между const и immutable](#8-7-взаимодействие-между-const-и-immutable) +- [8.8. Распространение квалификатора с параметра на результат](#8-8-распространение-квалификатора-с-параметра-на-результат) +- [8.9. Итоги](#8-9-итоги) + Квалификаторы типа выражают важные утверждения о типах языка. Эти утверждения исключительно полезны как для программиста, так и для компилятора, но их сложно выразить путем соглашений, обычного порождения подтипов (см. раздел 6.4.2) или параметризации типами (см. раздел 6.14). Показательный пример квалификатора типа – квалификатор типа `const` (введенный в языке C и доработанный в C++). Примененный к типу `T`, этот квалификатор выражает следующее утверждение: значения типа `T` можно инициализировать и читать, но не перезаписывать. Соблюдение этого ограничения гарантируется компилятором. Квалификатор `const` довольно полезен внутри модуля, поскольку гарантирует инициаторам вызовов регламентированное поведение функций. Например, сигнатура @@ -68,3 +79,485 @@ immutable pi = 3.14, val = 42; Для `pi` компилятор выводит тип `immutable(double)`, а для `val` – `immutable(int)`. [В начало ⮍](#8-1-квалификатор-immutable) [Наверх ⮍](#8-квалификаторы-типа) + +### 8.1.1. Транзитивность + +Любой тип можно определить с квалификатором `immutable`. Например: + +```d +struct Point { int x, y; } +auto origin = immutable(Point)(0, 0); +``` + +Поскольку для всех типов `T` справедливо, что `immutable(T)` – такой же тип, как любой другой, запись `immutable(Point)(0, 0)` является литералом структуры – так же как и `Point(0, 0)`. + +Неизменяемость естественным образом распространяется на все внутренние элементы объекта. Ведь пользователь ожидает, что если запрещено присваивание объекту `origin` в целом, то запрещено и присваивание полям `origin.x` и `origin.y`. Иначе было бы очень легко нарушить ограничение, налагаемое квалификатором immutable на объект в целом. + +```d +unittest +{ + auto anotherOrigin = immutable(Point)(1, 1); + origin = anotherOrigin; // Ошибка! + origin.x = 1; // Ошибка! + origin.y = 1; // Ошибка! +} +``` + +На самом деле, `immutable` *распространяется* абсолютно на каждое поле `Point`, тип каждого поля объекта квалифицируется тем же квалификатором, что и сам объект. Например, такой тест будет пройден: + +```d +static assert(is(typeof(origin.x) == immutable(int))); // Тест пройден +``` + +Но мир не настолько прост. Рассмотрим структуру, в которой есть некоторая косвенность, например поле массива: + +```d +struct DataSample +{ + int id; + double[] payload; +} +``` + +Очевидно, что поля объекта типа `immutable(DataSample)` не могут быть изменены. Но как насчет изменения элемента массива `payload`? + +```d +unittest +{ + auto ds = immutable(DataSample)(5, [ 1.0, 2.0 ]); + ds.payload[1] = 4.5; // ? +} +``` + +В данном случае может быть принято одно из двух возможных решений, у каждого из которых есть свои плюсы и минусы. Один из вариантов – сделать действие квалификатора поверхностным, то есть руководствоваться тем, что квалификатор immutable, примененный к структуре DataSample, применяется и ко всем ее непосредственным полям, но никак не влияет на данные, косвенно доступные через эти поля[^1]. Альтернативное решение – сделать неизменяемость транзитивной, что означало бы следующее: делая объект неизменяемым, вы также делаете неизменяемыми все данные, к которым можно обратиться через этот объект. Язык D пошел по второму пути. + +Транзитивная неизменяемость гораздо строже нетранзитивной. Определив неизменяемое значение, вы накладываете ограничение неизменяемости на целую сеть данных, связанную с этим значением (через ссылки, массивы и указатели). Таким образом, определять транзитивно неизменяемые значения гораздо сложнее, чем поверхностно неизменяемые. Но приложенные усилия окупаются сторицей. D выбрал транзитивную изменяемость по двум основным причинам: + +- *Функциональное программирование*. Словосочетание «функциональный стиль» каждый интерпретирует по-своему, но большинство согласятся, что отсутствие побочных эффектов – важный принцип. Обеспечить соблюдение этого ограничения лишь соглашением – значит отказаться от масштабируемости. Транзитивная неизменяемость позволяет программисту применять функциональный стиль для хорошо определенного фрагмента программы, а компилятору – проверять этот функциональный код на предмет непреднамеренных изменений данных. +- *Параллельное программирование*. Параллелизм – это огромная и очень сложная тема, занимаясь которой, ощущаешь нехватку твердых гарантий и неоспоримых истин. Неизменяемое разделение – один из таких островков уверенности: разделение неизменяемых данных между потоками всегда корректно, безопасно и эффективно. Чтобы позволить компилятору проверять, не изменяемы ли на самом деле разделяемые данные, неизменяемость должна быть транзитивной; в противном случае поток, обладающий доступом к неизменяемому фрагменту данных, может легко перейти к изменяемому разделению, попросту обратившись к косвенным полям этого фрагмента данных. + +Получив значение типа `immutable(T)`, можно быть абсолютно уверенным, что все, до чего можно добраться через это значение, также квалифицировано с помощью `immutable`, то есть неизменяемо. Более того, *никто* никогда не сможет изменить эти данные – данные, помеченные квалификатором `immutable`, все равно что впаяны. Это очень надежная гарантия, позволяющая, к примеру, беззаботно разделять такие неизменяемые данные между потоками. + +[В начало ⮍](#8-1-1-транзитивность) [Наверх ⮍](#8-квалификаторы-типа) + +## 8.2. Составление типов с помощью immutable + +Учитывая, что для точного выбора типа, который нужно квалифицировать, с квалификаторами используют скобки и что тип с квалификатором – это полноправный новый тип, можно сделать вывод, что, комбинируя ключевое слово `immutable` с другими конструкторами типов, можно создавать весьма сложные структуры данных. Сравним, например, следующие два типа: + +```d +alias immutable(int[]) T1; +alias immutable(int)[] T2; +``` + +В первом определении круглые скобки поглотили полностью весь тип массива; во втором случае затронут лишь тип элементов массива `int`, но не сам массив. Если при употреблении квалификатора `immutable` круглые скобки отсутствуют, квалификатор применяется ко всему типу, так что эквивалентное определение `T1` выглядит так: + +```d +alias immutable int[] T1; +``` + +Тип `T1` незамысловат: он представляет собой неизменяемый массив значений типа `int`. Само написание этого типа говорит то же самое. В соответствии со свойством транзитивности нельзя изменить ни массив в целом (например, присвоив переменной, содержащей массив, новый массив), ни какой-либо его элемент в отдельности: + +```d +T1 a = [ 1, 3, 5 ]; +T1 b = [ 2, 4 ]; +a = b; // Ошибка! +a[0] = b[1]; // Ошибка! +``` + +Второе определение кажется более тонким, но на самом деле понять его довольно просто, если вспомнить, что `immutable(int)` – самостоятельный тип. Тогда `immutable(int)[]` – это просто массив элементов этого самостоятельного типа, вот и все. Вывод о свойствах этого массива напрашивается сам. Можно присвоить значение массиву в целом, но нельзя изменить (в том числе через присваивание) отдельные его элементы: + +```d +T2 a = [ 1, 3, 5 ]; +T2 b = [ 2, 4 ]; +a = b; // Все в порядке +a[0] = b[1]; // Ошибка! +a ~= b; // Все в порядке (но как тонко!) +``` + +Может показаться странным, но добавление элементов в конец массива законно. Почему? Да просто потому, что эта операция не изменяет те элементы, которые в массиве уже есть. (Она может повлечь копирование данных, если потребуется переносить массив в другую область памяти, но в этом нет ничего страшного.) + +Как уже говорилось (см. раздел 4.5), `string` – это в действительности лишь псевдоним для типа `immutable(char)[]`. На самом деле, многие из полезных свойств типа `string`, задействованных в предыдущих главах, – заслуга квалификатора `immutable`. + +Сочетания ключевого слова `immutable` с параметрами-типами интерпретируются по той же логике. Предположим, есть обобщенный тип `Container!T`. Тогда в сочетании `immutable(Container!T)` квалификатор будет относиться ко всему контейнеру, а в сочетании `Container!(immutable(T))` – лишь к отдельным его элементам. + +[В начало ⮍](#8-2-составление-типов-с-помощью-immutable) [Наверх ⮍](#8-квалификаторы-типа) + +## 8.3. Неизменяемые параметры и методы + +В сигнатуре функции квалификатор `immutable` очень информативен. Рассмотрим одну из простейших функций: + +```d +string process(string input); +``` + +На самом деле это лишь краткая запись сигнатуры + +```d +immutable(char)[] process(immutable(char)[] input); +``` + +Функция `process` гарантирует, что не будет изменять отдельные знаки параметра `input`, так что инициатор вызова функции `process` может быть уверен, что после вызова строка окажется такой же, как и перед ним: + +```d +string s1 = "чепуха"; +string s2 = process(s1); +assert(s1 == "чепуха"); // Выполняется всегда +``` + +Более того, пользователь функции `process` может рассчитывать на то, что ее результат не сможет быть изменен: нет никаких скрытых псевдонимов, никакая другая функция не сможет позже изменить `s2`. В этом случае `immutable` также означает неизменяемость. + +Структуры и классы могут определять неизменяемые методы. В подобных случаях квалификатор применяется к `this`: + +```d +class A +{ + int[] fun(); // Обычный метод + int[] gun() immutable; // Можно вызвать, только если объект неизменяемый + immutable int[] hun(); // То же, что выше +} +``` + +Третья форма записи выглядит подозрительно: может показаться, что квалификатор `immutable` относится к `int[]`, но на самом деле он относится к `this`. Если нужно определить неизменяемый метод, возвращающий `immutable int[]`, получается что-то вроде заикания: + +```d +immutable immutable(int[]) iun(); +``` + +Поэтому в таких случаях ключевое слово `immutable` лучше писать в конце: + +```d +immutable(int[]) iun() immutable; +``` + +Разрешение оставить несколько сбивающий с толку `immutable` в начале определения продиктовано в основном стремлением унифицировать формат указания для всех свойств методов (таких как `final` или `static`). Например, определить сразу несколько неизменяемых методов, можно так: + +```d +class A +{ + immutable + { + int foo(); + int[] bar(); + void baz(); + } +} +``` + +Кроме того, квалификатор `immutable` можно использовать в виде метки: + +```d +class A +{ + immutable: + int foo(); + int[] bar(); + void baz(); +} +``` + +Разумеется, неизменяемые методы могут быть вызваны только применительно к неизменяемым объектам: + +```d +class C +{ + void fun() {} + void gun() immutable {} +} + +unittest +{ + auto c1 = new C; + auto c2 = new immutable(C); + c1.fun(); // Все в порядке + c2.gun(); // Все в порядке + // Никакие другие вызовы не сработают +} +``` + +[В начало ⮍](#8-3-неизменяемые-параметры-и-методы) [Наверх ⮍](#8-квалификаторы-типа) + +## 8.4. Неизменяемые конструкторы + +Работать с неизменяемым объектом совсем несложно, а вот его построение – весьма деликатный процесс. Причина в том, что в процессе построения нужно удовлетворить два противоречивых требования: 1) присвоить полям значения, 2) сделать их неизменяемыми. Поэтому D особенно внимателен к неизменяемым конструкторам. + +Проверка типов в неизменяемом конструкторе выполняется простым и осторожным способом. Компилятор разрешает присваивание полей только внутри конструктора, а чтение полей (включая передачу this в качестве аргумента при вызове метода) запрещено. Как только выполнение неизменяемого конструктора завершается, объект «замораживается» – после этого нельзя потребовать ни одного изменения. Вызов нестатического метода считается за чтение, поскольку такой метод обладает доступом к объекту this и способен прочесть любое его поле. (Компилятор не проверяет, читает ли метод поля на самом деле, – для перестраховки он предполагает, что метод все же читает некоторое поле.) + +Это правило строже, чем необходимо; ведь в действительности запрещается лишь присваивание значения полю после того, как это поле было прочитано. Однако это более строгое правило практически не мешает выразительности, при этом оно простое и понятное. Например: + +```d +class A +{ + int a; + int[] b; + this() immutable + { + a = 5; + b = [ 1, 2, 3 ]; + // Вызов fun() не был бы разрешен + } + void fun() immutable + { + ... + } +} +``` + +Вызывать из неизменяемого конструктора конструктор родителя `super` в порядке вещей, если этот вызов адресован также неизменяемому конструктору. Такие вызовы не угрожают нарушить неизменяемость. + +Инициализировать неизменяемые объекты обычно помогает рекурсия. К примеру, рассмотрим реализующий абстракцию односвязного списка класс, инициализируемый с помощью массива: + +```d +class List +{ + private int payload; + private List next; + this(int[] data) immutable + { + enforce(data.length); + payload = data[0]; + if (data.length == 1) return; + next = new immutable(List)(data[1 .. $]); + } +} +``` + +Чтобы корректно инициализировать хвост списка, конструктор рекурсивно вызывает сам себя с более коротким массивом. Попытки инициализировать список в цикле не пройдут компиляцию, поскольку проход по создаваемому списку означает чтение полей, а это запрещено. Рекурсия изящно решает эту проблему[^2]. + +[В начало ⮍](#8-4-неизменяемые-конструкторы) [Наверх ⮍](#8-квалификаторы-типа) + +## 8.5. Преобразования с участием immutable + +Рассмотрим пример кода: + +```d +unittest +{ + int a = 42; + immutable(int) b = a; + int c = b; +} +``` + +Более строгая система типизации не приняла бы этот код. Он включает два преобразования: сначала из `int` в `immutable(int)`, а затем обратно из `immutable(int)` в `int`. Собственно, по общим правилам эти преобразования незаконны. Например, если в этом коде заменить `int` на `int[]`, ни одно из следующих преобразований не будет корректным: + +```d +int[] a = [ 42 ]; +immutable(int[]) b = a; // Нет! +int[] c = b; // Нет! +``` + +Если бы такие преобразования были разрешены, неизменяемость бы не соблюдалась, поскольку тогда неизменяемые массивы разделяли бы свое содержимое с изменяемыми. + +Тем не менее компилятор распознает и разрешает некоторые автоматические преобразования между неизменяемыми и изменяемыми данными. А именно разрешено двунаправленное преобразование между `T` и `immutable(T)`, если у `T` «нет изменяемой косвенности». Интуитивно понятно, что «нет изменяемой косвенности» означает запрет перезаписывать косвенно доступные через `T` данные. Определение этого понятия рекурсивно: + +- у встроенных типов значений, таких как `int`, «нет изменяемой косвенности»; +- у массивов фиксированной длины из элементов типов, у которых «нет изменяемой косвенности», в свою очередь, тоже «нет изменяемой косвенности»; +- у массивов и указателей, ссылающихся на типы, у которых «нет изменяемой косвенности», тоже «нет изменяемой косвенности»; +- у структур, ни в одном поле которых «нет изменяемой косвенности», тоже «нет изменяемой косвенности». + +Например, у типа `S1` нет изменяемой косвенности, а у типа `S2` – есть: + +```d +struct S1 +{ + int a; + double[3] b; + string c; +} + +struct S2 +{ + int x; + float[] y; +} +``` + +Из-за поля `S2.y` у структуры `S2` есть изменяемая косвенность, так что преобразования вида `immutable(S2)` ↔ `S2` запрещены. Если бы они были разрешены, изменяемые и неизменяемые объекты стали бы некорректно разделять данные, хранимые в y, что нарушило бы гарантии, предоставленные квалификатором `immutable`. + +Вернемся к примеру, приведенному в начале этого раздела. Тип `int` не обладает изменяемой косвенностью, так что компилятор волен разрешить преобразования из `int` в `immutable(int)` и обратно. + +Чтобы определить такие преобразования для структуры, вам потребуется немного поработать вручную, направляя процесс в нужное русло. Вы предоставляете соответствующие конструкторы, а компилятор обеспечивает корректность вашего кода. Проще всего одолеть преобразование, заручившись поддержкой универсальной служебной функции преобразования `std.conv.to`[^3], которая понимает все тонкости преобразований типов с квалификаторами и всегда принимает соответственные меры. + +```d +import std.conv; + +struct S +{ + private int[] a; + // Преобразование из неизменяемого в изменяемое + this(immutable(S) source) + { + // Поместить дубликат массива в массив не-immutable + a = to!(int[])(source.a); + } + // Преобразование из изменяемого в неизменяемое + this(S source) immutable + { + // Поместить дубликат массива в массив immutable + a = to!(immutable(int[]))(source.a); + } + ... +} + +unittest +{ + S a; + auto b = immutable(S)(a); + auto c = S(b); +} +``` + +Преобразование не является неявным, но оно допустимо и безопасно. + +[В начало ⮍](#8-5-преобразования-с-участием-immutable) [Наверх ⮍](#8-квалификаторы-типа) + +## 8.6. Квалификатор const + +Немного поэкспериментировав с квалификатором типа `immutable`, мы видим, что этот квалификатор слишком строг, чтобы быть полезным в большинстве случаев. Да, если вы обязались не изменять определенные данные на протяжении работы целой программы, `immutable` вам подходит. Но часто неизменяемость – свойство, полезное в рамках модуля: хотелось бы оставить за собой право изменять некоторые данные, а остальным запретить это делать. Такие данные нельзя назвать неизменяемыми (то есть пометить квалификатором `immutable`), поскольку `immutable` означает: «Видите письмена, высеченные на камне? Это ваши данные». А вам нужно средство, чтобы выразить такое ограничение: «Вы не можете изменить эти данные, но кто-кто другой – может». Или, как сказал Алан Перлис: «Кому константа, а кому и переменная». Посмотрим, как система типов вполне серьезно реализует изречение Перлиса. + +Простой вариант использования таких данных – функция, например `print`, которая печатает какие-то данные. Функция `print` не изменяет переданные в нее данные, так что она допускает применение квалификатора `immutable`: + +```d +void print(immutable(int[]) data) { ... } + +unittest +{ + immutable(int[]) myData = [ 10, 20 ]; + print(myData); // Все в порядке +} +``` + +Отлично. Далее, пусть у нас есть значение типа `int[]`, которое мы только что вычислили и хотим напечатать. С таким аргументом наша функция не сработает, поскольку значение типа `int[]` не приводится к типу `immutable(int)[]` – а если бы приводилось, то возникла бы неподобающая общность изменяемых и якобы неизменяемых данных. Получается, что функция `print` не может напечатать данные типа `int[]`. Такое ограничение довольно неоправданно, поскольку `print` вообще не затрагивает свои аргументы, так что эта функция должна работать с неизменяемыми данными так же, как с изменяемыми. + +Что нам нужно, так это нечто вроде общего «либо изменяемого, либо нет» типа. В этом случае функцию `print` можно было бы объявить так: + +```d +void print(либо_изменяемый_либо_нет(int[]) data) { ... } +``` + +Только потому, что название `либо_изменяемый_либо_нет` несколько длинновато, для обозначения этого типа было введено ключевое слово `const`. Смысл абсолютно тот же: текущий код не может изменить значение типа `const(T)`, но есть вероятность, что это может сделать другой код. Такая двусмысленность отражает тот факт, что в роли `const(T)` может выступать как `T`, так и `immutable(T)`. Это качество квалификатора `const` делает его совершенным для организации взаимодействия между функциональным и обычным процедурным кодом. Продолжим начатый выше пример[^4]: + +```d +void print(const(int[]) data) { ... } + +unittest +{ + immutable(int[]) myData = [ 10, 20 ]; + print(myData); // Все в порядке + int[] myMutableData = [ 32, 42 ]; + print(myMutableData); // Все в порядке +} +``` + +Этот пример подразумевает, что как изменяемые, так и неизменяемые данные неявно преобразуются к `const`, что, в свою очередь, подразумевает нечто вроде взаимоотношения с подтипами. На самом деле, все так и есть: `const(T)` – это супертип и для типа `T`, и для типа `immutable(T)` (рис. 8.1). + +![image-8-6](images/image-8-6.png) + +***Рис. 8.1.*** *Для всех типов `T`: `const(T)` – супертип и для `T`, и для `immutable(T)`. Следовательно, код, работающий со значениями типа `const(T)`, принимает значения как изменяемого, так и неизменяемого типа `T`* + +Для квалификатора `const` верны те же правила транзитивности и преобразований, что и для квалификатора `immutable`. На конструкторы объектов `const`, в отличие от конструкторов `immutable`, ограничения не накладываются: внутри конструктора `const` объект считается изменяемым. + +Метод, объявленный с квалификатором `const`, может вызываться для объектов с любым квалификатором, так как он гарантирует, что ничего менять не будет, но не требует этого от объекта. Например: + +```d +class C +{ + void gun() const {} +} + +unittest +{ + auto c1 = new C; + auto c2 = new immutable(C); + auto c3 = new const(C); + c1.gun(); // Все в порядке + c2.gun(); // Все в порядке + c3.gun(); // Все в порядке +} +``` + +[В начало ⮍](#8-6-квалификатор-const) [Наверх ⮍](#8-квалификаторы-типа) + +## 8.7. Взаимодействие между const и immutable + +Нередко квалификатор пытается подействовать на тип, который уже находится под влиянием другого квалификатора. Например: + +```d +struct A +{ + const(int[]) c; + immutable(int[]) i; +} + +unittest +{ + const(A) ca; + immutable(A) ia; +} +``` + +Какие типы имеют поля `ca.i` и `ia.c`? Если бы квалификаторы применялись вслепую, получились бы типы `const(immutable(int[]))` и `immutable(const(int[]))` соответственно; очевидно, что-то тут лишнее, не говоря уже о типах `ca.c` и `ia.i`, когда один и тот же квалификатор применяется дважды! + +При наложении одного квалификатора на другой D руководствуется простыми правилами композиции. Если квалификаторы идентичны, они сокращаются до одного. В противном случае как `const(immutable(T))`, так и `immutable(const(T))` сокращаются до `immutable(T)`, поскольку это более строгий тип. Эти правила применяются при распространении на типы элементов массива; например, элементы массива `const(immutable(T)[])` имеют тип `immutable(T)`, а не `const(immutable(T))`. При этом тип самого массива несократим. + +[В начало ⮍](#8-7-взаимодействие-между-const-и-immutable) [Наверх ⮍](#8-квалификаторы-типа) + +## 8.8. Распространение квалификатора с параметра на результат + +C и C++ определяют поверхностный квалификатор `const` с неприятной особенностью: функция, возвращающая параметр, должна либо повторять свое определение дважды – для константных и неконстантных данных, либо вести опасную игру. Показательный пример такой функции – функция `strchr` из стандартной библиотеки C, которая в ней определена так: + +```d +char* strchr(const char* input, int c); +``` + +Эта функция очищает типы от квалификаторов: несмотря на то что `input` – константное значение, которое, если рассуждать наивно, не должно измениться, возвращение в выводе указателя, порожденного от `input`, снимает с данных это обещание. `strchr` способствует появлению кода, изменяющего неизменяемые данные без приведения типов. C++ избавился от этой проблемы, введя два определения `strchr`: + +```d +char* strchr(char* input, int c); +const char* strchr(const char* input, int c); +``` + +Эти функции делают одно и то же, но их нельзя соединить в одну, поскольку в C++ нет средств, позволяющих сказать: «Если у аргумента есть квалификатор, пожалуйста, распространите его и на тип возвращаемого значения». + +Для решения этой проблемы D предлагает «подстановочный» идентификатор квалификатора: `inout`. С участием `inout` объявление `strchr` выглядело бы так: + +```d +inout(char)* strchr(inout(char)* input, int c); +``` + +(Конечно же, в коде на D было бы предпочтительнее использовать массивы, а не указатели.) Компилятор понимает, что ключевое слово `inout` может быть заменено квалификатором `immutable`, `const` или ничем (последняя альтернатива имеет место в случае изменяемого входного значения). Он проверяет тело `strchr`, чтобы удостовериться в том, что код этой функции безопасно работает со всеми возможными типами входного значения. + +Квалификатор может быть перенесен с метода на его результат, например: + +```d +class X { ... } + +class Y +{ + private Y _another; + inout(Y) another() inout + { + enforce(_another !is null); + return _another; + } +} +``` + +Метод `another` принимает объекты с любым квалификатором. Этот метод можно переопределить, что очень примечательно, поскольку `inout` можно воспринимать как обобщенный параметр, а обобщенные методы обычно переопределять нельзя. Компилятор способен сделать метод с `inout` переопределяемым, поскольку может проверить, работает ли код в теле этого метода со всеми квалификаторами. + +[В начало ⮍](#8-8-распространение-квалификатора-с-параметра-на-результат) [Наверх ⮍](#8-квалификаторы-типа) + +## 8.9. Итоги + +Квалификаторы типа выражают важные свойства типов, которые другие механизмы абстрагирования не позволяют выразить. Основное внимание в главе было уделено квалификатору типа `immutable`, предоставляющему очень надежные гарантии: неизменяемое значение за все время его жизни никогда не сможет быть изменено, транзитивно. Это очень полезное свойство. Оно позволяет обеспечить чисто функциональную семантику и помогает организовать безопасное разделение данных между потоками. + +Сила квалификатора `immutable` также является его слабостью: он не допускает использование нескольких шаблонов обработки данных, когда за запись информации отвечают одни, а за ее чтение – другие. Эту проблему решает квалификатор `const`, выражающий контекстную неизменяемость: собственник значения `const` не может изменять данные, но другие части программы могут обладать этим правом. + +Наконец, чтобы избежать повторения идентичного кода для функций, принимающих параметры с квалификатором и без квалификатора, был введен подстановочный квалификатор `inout`. Вместо ключевого слова `inout` подставляется `immutable`, `const` или пустое место при отсутствии квалификатора. + +[В начало ⮍](#8-9-итоги) [Наверх ⮍](#8-квалификаторы-типа) + +[^1]: Такой подход был избран для квалификатора `const` в C++. +[^2]: Это решение было предложено Саймоном Пейтоном-Джонсом. +[^3]: Кроме того, у любого массива `T[]`, `const(T)[]` и `immutable(T)[]` есть свойство `dup`, возвращающее копию массива типа `T[]`, и свойство `idup`, возвращающее копию типа `immutable(T)[]`. – *Прим. науч. ред.* +[^4]: Приведенную ниже функцию можно было бы объявить как `void print(in int[] data);`, что означает в точности то же самое, но несколько лучше смотрится. – *Прим. науч. ред.* diff --git a/08-квалификаторы-типа/images/image-8-6.png b/08-квалификаторы-типа/images/image-8-6.png new file mode 100644 index 0000000000000000000000000000000000000000..60f98d796b4c420410ec4360cdffa6fb29801cc4 GIT binary patch literal 11550 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