Вычисление констант (Constant evaluation)
Вычисление констант - это процесс вычисления результата выражений во время компиляции. Только подмножество всех выражений может быть вычислено во время компиляции.
Выражения констант
Определенные формы выражений, называемые выражениями констант, могут быть вычислены во время компиляции.
В контекстах const это единственные разрешенные выражения, и они всегда вычисляются во время компиляции.
В других местах, таких как let statements, выражения констант могут быть, но не гарантированно, вычислены во время компиляции.
Поведения, такие как выход за границы индексации массива или переполнение, являются ошибками компилятора, если значение должно быть вычислено во время компиляции (т.е. в контекстах const). В противном случае, эти поведения являются предупреждениями, но, вероятно, вызовут панику во время выполнения.
Следующие выражения являются выражениями констант, при условии, что любые операнды также
являются выражениями констант и не вызывают запуск каких-либо вызовов Drop::drop.
- Пути к функциям и константам. Рекурсивное определение констант не разрешено.
-
Пути к статическим переменным с этими ограничениями:
- Запись в
staticэлементы не разрешена в любом контексте вычисления констант. - Чтение из
externстатических переменных не разрешено в любом контексте вычисления констант. - Если вычисление не выполняется в инициализаторе
staticэлемента, то чтение из любой изменяемойstaticне разрешено. Изменяемаяstatic- это элементstatic mutилиstaticэлемент с внутренне-изменяемым типом.
Эти требования проверяются только когда константа вычисляется. Другими словами, синтаксическое наличие таких обращений в контекстах const разрешено, пока они никогда не выполняются.
- Запись в
- Блочные выражения, включая
unsafeиconstблоки.- let statements и, следовательно, неопровержимые patterns, включая изменяемые привязки
- выражения присваивания
- составные выражения присваивания
- выражения-операторы
- Выражения индекса, индексация массива или среза с
usize.
- Выражения замыканий, которые не захватывают переменные из окружения.
- Встроенные операторы отрицания, арифметические, логические, сравнения или ленивые булевы
операторы, используемые на целочисленных и типах с плавающей точкой,
boolиchar.
-
Все формы заимствований, включая сырые заимствования, за исключением заимствований выражений, временные области видимости которых были бы расширены (см. расширение времени жизни временных) до конца программы и которые являются либо:
- Изменяемыми заимствованиями.
- Разделяемыми заимствованиями выражений, которые приводят к значениям с внутренней изменяемостью.
#![allow(unused)] fn main() { // Из-за хвостовой позиции это заимствование расширяет область видимости // временного до конца программы. Поскольку заимствование изменяемое, // это не разрешено в выражении константы. const C: &u8 = &mut 0; // ОШИБКА не разрешено }#![allow(unused)] fn main() { // Const блоки похожи на инициализаторы `const` элементов. let _: &u8 = const { &mut 0 }; // ОШИБКА не разрешено }#![allow(unused)] fn main() { use core::sync::atomic::AtomicU8; // Это не разрешено, так как 1) временная область видимости расширена до // конца программы и 2) временный объект имеет внутреннюю изменяемость. const C: &AtomicU8 = &AtomicU8::new(0); // ОШИБКА не разрешено }#![allow(unused)] fn main() { use core::sync::atomic::AtomicU8; // Как выше. let _: &_ = const { &AtomicU8::new(0) }; // ОШИБКА не разрешено }#![allow(unused)] fn main() { #![allow(static_mut_refs)] // Несмотря на то, что это заимствование изменяемое, оно не временного объекта, так что // это разрешено. const C: &u8 = unsafe { static mut S: u8 = 0; &mut S }; // OK }#![allow(unused)] fn main() { use core::sync::atomic::AtomicU8; // Несмотря на то, что это заимствование значения с внутренней изменяемостью, // оно не временного объекта, так что это разрешено. const C: &AtomicU8 = { static S: AtomicU8 = AtomicU8::new(0); &S // OK }; }#![allow(unused)] fn main() { use core::sync::atomic::AtomicU8; // Это разделяемое заимствование внутренне изменяемого временного объекта разрешено // потому что его область видимости не расширена. const C: () = { _ = &AtomicU8::new(0); }; // OK }#![allow(unused)] fn main() { // Несмотря на то, что заимствование изменяемое и временный объект живет до // конца программы из-за промоута, это разрешено, потому что // заимствование не в хвостовой позиции и поэтому область видимости временного // не расширена через расширение времени жизни временных. const C: () = { let _: &'static mut [u8] = &mut []; }; // OK // ~~ // Промоутированный временный объект. }Note
Другими словами — чтобы сосредоточиться на том, что разрешено, а не на том, что запрещено — разделяемые заимствования внутренне изменяемых данных и изменяемые заимствования разрешены в контексте const только когда заимствованное выражение места является транзитным, косвенным или статическим.
Выражение места является транзитным, если это переменная, локальная для текущего контекста const, или выражение, временная область видимости которого содержится внутри текущего контекста const.
#![allow(unused)] fn main() { // Заимствование переменной, локальной для инициализатора, следовательно // это выражение места транзитное. const C: () = { let mut x = 0; _ = &mut x; }; }#![allow(unused)] fn main() { // Заимствование временного объекта, область видимости которого не была расширена, // следовательно это выражение места транзитное. const C: () = { _ = &mut 0u8; }; }#![allow(unused)] fn main() { // Когда временный объект промоутирован, но не расширен по времени жизни, его // выражение места все еще рассматривается как транзитное. const C: () = { let _: &'static mut [u8] = &mut []; }; }Выражение места является косвенным, если это выражение разыменования.
#![allow(unused)] fn main() { const C: () = { _ = &mut *(&mut 0); }; }Выражение места является статическим, если это
staticэлемент.#![allow(unused)] fn main() { #![allow(static_mut_refs)] const C: &u8 = unsafe { static mut S: u8 = 0; &mut S }; }Note
Одним неожиданным следствием этих правил является то, что мы разрешаем это,
#![allow(unused)] fn main() { const C: &[u8] = { let x: &mut [u8] = &mut []; x }; // OK // ~~~~~~~ // Пустые массивы промоутируются даже за изменяемыми заимствованиями. }но мы запрещаем этот похожий код:
#![allow(unused)] fn main() { const C: &[u8] = &mut []; // ERROR // ~~~~~~~ // Хвостовое выражение. }Разница между ними в том, что в первом пустой массив промоутирован, но его область видимости не подвергается расширению времени жизни временных, поэтому мы считаем выражение места транзитным (даже если после промоута место действительно живет до конца программы). Во втором область видимости временного пустого массива подвергается расширению времени жизни, и поэтому он отвергается из-за того, что является изменяемым заимствованием временного с расширенным временем жизни (и, следовательно, заимствует нетранзитное выражение места).
Эффект неожиданный, потому что расширение времени жизни временных в этом случае приводит к тому, что меньше кода компилируется, чем без него.
См. issue #143129 для более подробной информации.
- Оператор разыменования, за исключением сырых указателей.
- Группированные выражения.
- Выражения приведения, за исключением
- приведения указателя к адресу и
- приведения указателя на функцию к адресу.
- Вызовы const функций и const методов.
Контекст const
Контекст const - это один из следующих:
- Инициализатор
Контексты const, которые используются как части типов (выражения длины типа массива и повторения, а также аргументы const обобщений), могут только ограниченно использовать окружающие обобщенные параметры: такое выражение должно быть либо единственным голым const обобщенным параметром, либо произвольным выражением, не использующим никаких обобщений.
Const функции
Const функция - это функция, которая может быть вызвана из контекста const. Она определяется с квалификатором const и также включает конструкторы кортежных структур и вариантов кортежных перечислений.
Example
#![allow(unused)] fn main() { const fn square(x: i32) -> i32 { x * x } const VALUE: i32 = square(12); }
При вызове из контекста const, const функция интерпретируется компилятором во время компиляции. Интерпретация происходит в среде цели компиляции, а не хоста. Так что usize - это 32 бита, если вы компилируете для 32 битной системы, независимо от того, собираете ли вы на 64 битной или 32 битной системе.
Когда const функция вызывается извне контекста const, она ведет себя так же, как если бы у нее не было квалификатора const.
Тело const функции может использовать только выражения констант.
Const функции не могут быть async.
Типы параметров const функции и тип возвращаемого значения ограничены теми, которые совместимы с контекстом const.