7.7 KiB
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)
.