Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Вычисление констант (Constant evaluation)

Вычисление констант - это процесс вычисления результата выражений во время компиляции. Только подмножество всех выражений может быть вычислено во время компиляции.

Выражения констант

Определенные формы выражений, называемые выражениями констант, могут быть вычислены во время компиляции.

В контекстах const это единственные разрешенные выражения, и они всегда вычисляются во время компиляции.

В других местах, таких как let statements, выражения констант могут быть, но не гарантированно, вычислены во время компиляции.

Поведения, такие как выход за границы индексации массива или переполнение, являются ошибками компилятора, если значение должно быть вычислено во время компиляции (т.е. в контекстах const). В противном случае, эти поведения являются предупреждениями, но, вероятно, вызовут панику во время выполнения.

Следующие выражения являются выражениями констант, при условии, что любые операнды также являются выражениями констант и не вызывают запуск каких-либо вызовов Drop::drop.

  • Пути к статическим переменным с этими ограничениями:

    • Запись в static элементы не разрешена в любом контексте вычисления констант.
    • Чтение из extern статических переменных не разрешено в любом контексте вычисления констант.
    • Если вычисление не выполняется в инициализаторе static элемента, то чтение из любой изменяемой static не разрешено. Изменяемая static - это элемент static mut или static элемент с внутренне-изменяемым типом.

    Эти требования проверяются только когда константа вычисляется. Другими словами, синтаксическое наличие таких обращений в контекстах const разрешено, пока они никогда не выполняются.

  • Все формы заимствований, включая сырые заимствования, за исключением заимствований выражений, временные области видимости которых были бы расширены (см. расширение времени жизни временных) до конца программы и которые являются либо:

    • Изменяемыми заимствованиями.
    • Разделяемыми заимствованиями выражений, которые приводят к значениям с внутренней изменяемостью.
    #![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 и также включает конструкторы кортежных структур и вариантов кортежных перечислений.

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.