常量求值
常量求值是在编译期间计算表达式结果的过程。只有一部分表达式可以在编译时求值。
常量表达式
某些形式的表达式(称为常量表达式)可以在编译时求值。
常量上下文中的表达式必须是常量表达式。
常量上下文中的表达式始终在编译时求值。
在常量上下文之外,常量表达式可能在编译时求值,但不保证。
如果值必须在编译时求值(即在常量上下文中),则越界数组索引或溢出等行为是编译器错误。否则,这些行为是警告,但可能在运行时 panic。
以下表达式是常量表达式,只要任何操作数也是常量表达式且不会导致任何 Drop::drop 调用运行。
- 字面量。
-
到静态的路径,有以下限制:
- 在任何常量求值上下文中不允许写入
static项。 - 在任何常量求值上下文中不允许读取
extern静态。 - 如果求值不是在
static项的初始化器中进行的,则不允许读取任何可变static。可变static是static mut项或具有内部可变类型的static项。
这些要求仅在常量被求值时检查。换句话说,只要它们从未被执行,在常量上下文中语法上出现此类访问是允许的。
- 在任何常量求值上下文中不允许写入
- 数组和切片索引表达式,其中索引是
usize。
- 不从环境捕获变量的闭包表达式。
-
所有形式的借用,包括原始借用,但以下情况除外:其临时作用域将被扩展(参见临时生命周期扩展)到程序末尾的表达式的借用,并且它们是:
- 可变借用。
- 导致具有内部可变性的值的表达式的共享借用。
#![allow(unused)] fn main() { // Due to being in tail position, this borrow extends the scope of the // temporary to the end of the program. Since the borrow is mutable, // this is not allowed in a const expression. const C: &u8 = &mut 0; // ERROR not allowed }#![allow(unused)] fn main() { // Const blocks are similar to initializers of `const` items. let _: &u8 = const { &mut 0 }; // ERROR not allowed }#![allow(unused)] fn main() { use core::sync::atomic::AtomicU8; // This is not allowed as 1) the temporary scope is extended to the // end of the program and 2) the temporary has interior mutability. const C: &AtomicU8 = &AtomicU8::new(0); // ERROR not allowed }#![allow(unused)] fn main() { use core::sync::atomic::AtomicU8; // As above. let _: &_ = const { &AtomicU8::new(0) }; // ERROR not allowed }#![allow(unused)] fn main() { #![allow(static_mut_refs)] // Even though this borrow is mutable, it's not of a temporary, so // this is allowed. const C: &u8 = unsafe { static mut S: u8 = 0; &mut S }; // OK }#![allow(unused)] fn main() { use core::sync::atomic::AtomicU8; // Even though this borrow is of a value with interior mutability, // it's not of a temporary, so this is allowed. const C: &AtomicU8 = { static S: AtomicU8 = AtomicU8::new(0); &S // OK }; }#![allow(unused)] fn main() { use core::sync::atomic::AtomicU8; // This shared borrow of an interior mutable temporary is allowed // because its scope is not extended. const C: () = { _ = &AtomicU8::new(0); }; // OK }#![allow(unused)] fn main() { // Even though the borrow is mutable and the temporary lives to the // end of the program due to promotion, this is allowed because the // borrow is not in tail position and so the scope of the temporary // is not extended via temporary lifetime extension. const C: () = { let _: &'static mut [u8] = &mut []; }; // OK // ~~ // Promoted temporary. }Note
换句话说 — 关注什么是允许的而不是什么是不允许的 — 内部可变数据的共享借用和可变借用仅在借用的位置表达式是瞬态的、间接的或静态的时才在常量上下文中允许。
位置表达式是瞬态的,如果它是当前常量上下文的局部变量或其临时作用域包含在当前常量上下文中的表达式。
#![allow(unused)] fn main() { // The borrow is of a variable local to the initializer, therefore // this place expression is transient. const C: () = { let mut x = 0; _ = &mut x; }; }#![allow(unused)] fn main() { // The borrow is of a temporary whose scope has not been extended, // therefore this place expression is transient. const C: () = { _ = &mut 0u8; }; }#![allow(unused)] fn main() { // When a temporary is promoted but not lifetime extended, its // place expression is still treated as transient. 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 // ~~~~~~~ // Empty arrays are promoted even behind mutable borrows. }但我们禁止这种类似的代码:
#![allow(unused)] fn main() { const C: &[u8] = &mut []; // ERROR // ~~~~~~~ // Tail expression. }它们之间的区别在于,在第一个中,空数组被提升但其作用域不经历临时生命周期扩展,因此我们认为位置表达式是瞬态的(即使提升后该位置确实存活到程序末尾)。在第二个中,空数组临时的作用域确实经历了生命周期扩展,因此由于它是生命周期扩展临时的可变借用(因此借用非瞬态位置表达式)而被拒绝。
此效果令人惊讶,因为在这种情况下,临时生命周期扩展导致比没有它时更少的代码能够编译。
有关更多详细信息,请参阅 issue #143129。
-
#![allow(unused)] fn main() { use core::cell::UnsafeCell; const _: u8 = unsafe { let x: *mut u8 = &raw mut *&mut 0; // ^^^^^^^ // Dereference of mutable reference. *x = 1; // Dereference of mutable pointer. *(x as *const u8) // Dereference of constant pointer. }; const _: u8 = unsafe { let x = &UnsafeCell::new(0); *x.get() = 1; // Mutation of interior mutable value. *x.get() }; }
- 分组表达式。
- 强制转换表达式,以下情况除外:
- 指针到地址的转换和
- 函数指针到地址的转换。
- 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 函数由编译器在编译时解释。解释发生在编译目标的环境中,而不是主机中。因此,如果您针对 32 位系统编译,usize 是 32 位,无论您是在 64 位还是 32 位系统上构建。
从常量上下文外部调用 const 函数时,其行为与没有 const 限定符时相同。
const 函数的主体只能使用常量表达式。
不允许 const 函数是异步的。
const 函数的参数和返回类型的类型仅限于与常量上下文兼容的类型。