| .. | ||
| README.md | ||
8. Квалификаторы типа
Квалификаторы типа выражают важные утверждения о типах языка. Эти утверждения исключительно полезны как для программиста, так и для компилятора, но их сложно выразить путем соглашений, обычного порождения подтипов (см. раздел 6.4.2) или параметризации типами (см. раздел 6.14).
Показательный пример квалификатора типа – квалификатор типа const (введенный в языке C и доработанный в C++). Примененный к типу T, этот квалификатор выражает следующее утверждение: значения типа T можно инициализировать и читать, но не перезаписывать. Соблюдение этого ограничения гарантируется компилятором. Квалификатор const довольно полезен внутри модуля, поскольку гарантирует инициаторам вызовов регламентированное поведение функций. Например, сигнатура
// Функция из стандартной библиотеки C
int printf(const char * format, ...);
обещает пользователям, что функция printf не будет пытаться изменить знаки, переданные в параметре format. Подобная гарантия также полезна при масштабной разработке, поскольку сокращает количество зависимостей, созданных немодульными изменениями. Определить такие ограничения и гарантировать подчинение им можно и посредством соглашения, но подобные соглашения неудобны, и соблюдать их трудно. D определяет три типа квалификаторов:
constозначает неизменяемость в рамках заданного контекста. Значение типа, заданного с ключевым словомconst, нельзя изменить напрямую. Однако другие сущности в программе могут обладать правом перезаписывать эти данные: так у инициатора вызова функцииprintfможет быть право записи в переменнуюformat, а у самой функции – нет.immutableозначает абсолютную, контекстно-независимую неизменяемость. Значение типа, заданного с ключевым словомimmutable, после инициализации нельзя изменить ни при каких обстоятельствах нигде в программе. Это гораздо более строгое ограничение, чем у квалификатораconst.sharedозначает разделение значения между потоками.
Все они дополняют друг друга. Квалификаторы const и immutable важны для масштабной разработки. Кроме того, без квалификатора immutable невозможно было бы программировать в функциональном стиле, а квалификатор const способствует интеграции кода в функциональном стиле с кодом в объектно-ориентированном и процедурном стиле. Квалификаторы immutable и shared позволяют реализовать многопоточность. Подробное описание квалификатора shared и разговор о многопоточности мы отложим до главы 13. А здесь сосредоточимся на квалификаторах const и immutable.
8.1. Квалификатор immutable
Значение типа с квалификатором immutable высечено на камне: сразу же после инициализации такого значения можно считать, что оно навечно прожжено в хранящей его памяти. Оно никогда не изменится за все время исполнения программы.
Форма записи типа с квалификатором такова: ‹квалификатор›(T), где ‹квалификатор› – одно из ключевых слов immutable, const и shared. Например, определим неизменяемое целое число:
immutable(int) forever = 42;
Попытки каким-либо способом изменить значение переменной forever приведут к ошибке во время компиляции. Более того, immutable(int) – это полноправный тип, как любой другой тип (он отличается от типа int). Например, можно присвоить ему псевдоним:
alias immutable(int) StableInt;
StableInt forever = 42;
Определяя копию переменной forever с ключевым словом auto, вы распространите тип immutable(int) и на копию, так что и сама копия будет неизменяемым целым числом. Ничего особенного здесь нет, но именно этим отличаются квалификаторы типов и простые классы памяти, такие как static (см. раздел 5.2.5) или ref (см. раздел 5.2.1).
unittest
{
immutable(int) forever = 42;
auto andEver = forever;
++andEver; // Ошибка! Нельзя изменять неизменяемое значение!
}
Значение типа с квалификатором immutable необязательно инициализировать константой, известной во время компиляции:
void fun(int x)
{
immutable(int) xEntry = x;
...
}
Примененный таким образом квалификатор immutable оказывает услугу тем, кто будет разбираться в работе функции fun. С первого взгляда понятно, что переменная xEntry будет хранить переданное на входе в функцию значение x от начала и до конца тела этой функции.
В определениях с квалификатором immutable необязательно указывать тип – он будет определен так же, как если бы вместо immutable стояло ключевое слово auto:
immutable pi = 3.14, val = 42;
Для pi компилятор выводит тип immutable(double), а для val – immutable(int).