Константные элементы
Syntax
ConstantItem →
const ( IDENTIFIER | _ ) : Type ( = Expression )? ;
Константный элемент - это опционально именованное константное значение, которое не связано с конкретным местом в памяти программы.
Константы по сути встраиваются везде, где они используются, что означает, что они копируются непосредственно в соответствующий
контекст при использовании. Это включает использование констант из внешних крейтов и
не-Copy типы. Ссылки на одну и ту же константу не обязательно
гарантированно ссылаются на один и тот же адрес памяти.
Объявление константы определяет константное значение в пространстве имён значений модуля или блока, где она находится.
Константы должны быть явно типизированы. Тип должен иметь время жизни 'static: любые
ссылки в инициализаторе должны иметь время жизни 'static. Ссылки
в типе константы по умолчанию имеют время жизни 'static; см. статическое элизирование времени жизни.
Ссылка на константу будет иметь время жизни 'static, если значение константы подходит для
продвижения; в противном случае будет создана временная переменная.
#![allow(unused)] fn main() { const BIT1: u32 = 1 << 0; const BIT2: u32 = 1 << 1; const BITS: [u32; 2] = [BIT1, BIT2]; const STRING: &'static str = "bitstring"; struct BitsNStrings<'a> { mybits: [u32; 2], mystring: &'a str, } const BITS_N_STRINGS: BitsNStrings<'static> = BitsNStrings { mybits: BITS, mystring: STRING, }; }
Финальное значение элемента const, после вычисления инициализатора до значения, которое имеет объявленный тип константы, не может содержать никаких изменяемых ссылок, кроме случаев, описанных ниже.
#![allow(unused)] fn main() { #![allow(static_mut_refs)] static mut S: u8 = 0; const _: &u8 = unsafe { &mut S }; // OK. // ^^^^^^ // Разрешено, так как это приводится к `&S`. }
#![allow(unused)] fn main() { use core::sync::atomic::AtomicU8; static S: AtomicU8 = AtomicU8::new(0); const _: &AtomicU8 = &S; // OK. // ^^ // Разрешено, даже несмотря на то, что разделяемая ссылка указывает на внутренне // изменяемое значение. }
#![allow(unused)] fn main() { #![allow(static_mut_refs)] static mut S: u8 = 0; const _: &mut u8 = unsafe { &mut S }; // ОШИБКА. // ^^^^^^ // Не разрешено, так как изменяемая ссылка появляется в финальном значении. }
Note
Инициализаторы констант можно рассматривать, в большинстве случаев, как встраиваемые везде, где появляется константа. Если бы константа, значение которой содержит изменяемую ссылку на изменяемую статическую переменную, появлялась дважды, и это было бы разрешено, это создало бы две изменяемые ссылки, каждая с временем жизни
'static, на одно и то же место. Это могло бы привести к неопределённому поведению.Константы, которые содержат изменяемые ссылки на временные переменные, чьи области видимости были расширены до конца программы, имеют ту же проблему и дополнительную.
#![allow(unused)] fn main() { const _: &mut u8 = &mut 0; // ОШИБКА. // ^^^^^^ // Не разрешено, так как изменяемая ссылка появляется в финальном значении и // потому что константное выражение содержит изменяемое заимствование // выражения, чья временная область видимости была бы расширена до конца // программы. }Здесь значение
0является временной переменной, чья область видимости расширена до конца программы (см. destructors.scope.lifetime-extension.static). Такие временные переменные не могут быть изменяемо заимствованы в константных выражениях (см. const-eval.const-expr.borrows).Чтобы разрешить это, нам пришлось бы решить, создаёт ли каждое использование константы новое значение
u8или каждое использование разделяет одну и ту же временную переменную с расширенным временем жизни. Последний выбор, хотя ближе к тому, какrustcдумает об этом сегодня, нарушил бы концептуальную модель, что в большинстве случаев инициализатор константы можно рассматривать как встраиваемый везде, где используется константа. Поскольку мы не решили, и из-за другой упомянутой проблемы, это не разрешено.
#![allow(unused)] fn main() { #![allow(static_mut_refs)] static mut S: u8 = 0; const _: &dyn Send = &unsafe { &mut S }; // ОШИБКА. // ^^^^^^ // Не разрешено, так как изменяемая ссылка появляется в финальном значении, // даже несмотря на стирание типа. }
Изменяемые ссылки, где указываемый объект является значением типа нулевого размера, разрешены.
#![allow(unused)] fn main() { #![allow(static_mut_refs)] static mut S: () = (); const _: &mut () = unsafe { &mut S }; // OK. // ^^ Это тип нулевого размера. }
#![allow(unused)] fn main() { #![allow(static_mut_refs)] static mut S: [u8; 0] = [0; 0]; const _: &mut [u8; 0] = unsafe { &mut S }; // OK. // ^^^^^^^ Это тип нулевого размера. }
Note
Это разрешено, так как для значения типа нулевого размера никакие байты не могут быть фактически изменены. Мы должны принять это, так как
&mut []продвигается.
Значения типа объединения не считаются содержащими какие-либо ссылки; для этой цели значение типа объединения обрабатывается как последовательность нетипизированных байтов.
#![allow(unused)] fn main() { #![allow(static_mut_refs)] union U { f: &'static mut u8 } static mut S: u8 = 0; const _: U = unsafe { U { f: &mut S }}; // OK. // ^^^^^^^^^^^^^^^ // Это обрабатывается как последовательность нетипизированных байтов. }
Изменяемые ссылки, содержащиеся внутри изменяемой статической переменной, могут быть ссылаемы в финальном значении константы.
#![allow(unused)] fn main() { #![allow(static_mut_refs)] static mut S: &mut u8 = unsafe { static mut I: u8 = 0; &mut I }; const _: &&mut u8 = unsafe { &S }; // OK. // ^^^^^^^ // Эта изменяемая ссылка происходит из `static mut`. }
Note
Это разрешено, так как отдельно не разрешено читать из изменяемой статической переменной во время вычисления константы. См. const-eval.const-expr.path-static.
Изменяемые ссылки, содержащиеся внутри внешней статической переменной, могут быть ссылаемы в финальном значении константы.
#![allow(unused)] fn main() { #![allow(static_mut_refs)] unsafe extern "C" { static S: &'static mut u8; } const _: &&mut u8 = unsafe { &S }; // OK. // ^^^^^^^ // Эта изменяемая ссылка происходит из внешней статической переменной. }
Note
Это разрешено, так как отдельно не разрешено читать из внешней статической переменной во время вычисления константы. См. const-eval.const-expr.path-static.
Note
Как описано выше, мы принимаем в финальном значении константных элементов разделяемые ссылки на статические элементы, чьи значения имеют внутреннюю изменяемость.
#![allow(unused)] fn main() { use core::sync::atomic::AtomicU8; static S: AtomicU8 = AtomicU8::new(0); const _: &AtomicU8 = &S; // OK. }Однако мы запрещаем аналогичный код, когда внутренне изменяемое значение создаётся в инициализаторе.
#![allow(unused)] fn main() { use core::sync::atomic::AtomicU8; const _: &AtomicU8 = &AtomicU8::new(0); // ОШИБКА. }Здесь
AtomicU8является временной переменной, чья область видимости расширена до конца программы (см. destructors.scope.lifetime-extension.static). Такие временные переменные с внутренней изменяемостью не могут быть заимствованы в константных выражениях (см. const-eval.const-expr.borrows).Чтобы разрешить это, нам пришлось бы решить, создаёт ли каждое использование константы новый
AtomicU8или каждое использование разделяет одну и ту же временную переменную с расширенным временем жизни. Последний выбор, хотя ближе к тому, какrustcдумает об этом сегодня, нарушил бы концептуальную модель, что в большинстве случаев инициализатор константы можно рассматривать как встраиваемый везде, где используется константа. Поскольку мы не решили, это не разрешено.
Константное выражение может быть опущено только в определении трейта.
Константы с деструкторами
Константы могут содержать деструкторы. Деструкторы запускаются, когда значение выходит из области видимости.
#![allow(unused)] fn main() { struct TypeWithDestructor(i32); impl Drop for TypeWithDestructor { fn drop(&mut self) { println!("Dropped. Held {}.", self.0); } } const ZERO_WITH_DESTRUCTOR: TypeWithDestructor = TypeWithDestructor(0); fn create_and_drop_zero_with_destructor() { let x = ZERO_WITH_DESTRUCTOR; // x удаляется в конце функции, вызывая drop. // выводит "Dropped. Held 0.". } }
Безымянные константы
В отличие от ассоциированной константы, свободная константа может быть безымянной с использованием подчёркивания вместо имени. Например:
#![allow(unused)] fn main() { const _: () = { struct _SameNameTwice; }; // OK, хотя это то же имя, что и выше: const _: () = { struct _SameNameTwice; }; }
Как и с импортами с подчёркиванием, макросы могут безопасно выдавать одну и ту же безымянную константу в той же области видимости более одного раза. Например, следующее не должно производить ошибку:
#![allow(unused)] fn main() { macro_rules! m { ($item: item) => { $item $item } } m!(const _: () = ();); // Это раскрывается в: // const _: () = (); // const _: () = (); }
Вычисление
Свободные константы всегда вычисляются во время компиляции для выявления паник. Это происходит даже внутри неиспользуемой функции:
#![allow(unused)] fn main() { // Паника во время компиляции const PANIC: () = std::unimplemented!(); fn unused_generic_function<T>() { // Неудачная проверка во время компиляции const _: () = assert!(usize::BITS == 0); } }