常量求值
常量求值是在编译期间计算表达式结果的过程。只有所有表达式的一个子集可以在编译时求值。
常量表达式
某些形式的表达式(称为常量表达式)可以在编译时求值。
const 上下文中的表达式必须是常量表达式。
const 上下文中的表达式始终在编译时求值。
在 const 上下文之外,常量表达式可能在编译时求值,但不保证。
如果值必须在编译时求值(即在 const 上下文中),诸如越界数组索引或溢出等行为是编译器错误。否则,这些行为是警告,但在运行时可能会导致 panic。
以下表达式是常量表达式,只要任何操作数也是常量表达式,并且不会导致运行任何 Drop::drop 调用。
- 字面量。
- 常量参数。
-
指向静态项的路径,有以下限制:
- 在任何常量求值上下文中不允许写入
static项。 - 在任何常量求值上下文中不允许读取
extern静态项。 - 如果求值不在
static项的初始化器中进行,则不允许读取任何可变static。可变static是static mut项,或具有内部可变类型的static项。
这些要求仅在常量被求值时检查。换句话说,在 const 上下文中语法上存在此类访问是允许的,只要它们从未被执行。
- 在任何常量求值上下文中不允许写入
- 数组和切片索引表达式,其中索引是
usize。
- 不从环境中捕获变量的闭包表达式。
-
所有形式的借用,包括原始借用,除了那些临时作用域会被延长到程序结束(见临时生命周期延长)的表达式的借用,并且这些表达式要么是:
- 可变借用。
- 对产生具有内部可变性值的表达式的共享借用。
#![allow(unused)] fn main() { // 由于处于尾部位置,此借用将临时值的作用域延长到程序结束。 // 由于借用是可变的,这在 const 表达式中不允许。 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 }; // 正确 }#![allow(unused)] fn main() { use core::sync::atomic::AtomicU8; // 即使此借用是对具有内部可变性值的借用, // 它不是临时值的借用,因此允许。 const C: &AtomicU8 = { static S: AtomicU8 = AtomicU8::new(0); &S // 正确 }; }#![allow(unused)] fn main() { use core::sync::atomic::AtomicU8; // 此对内部可变临时值的共享借用是允许的, // 因为其作用域未被延长。 const C: () = { _ = &AtomicU8::new(0); }; // 正确 }#![allow(unused)] fn main() { // 即使借用是可变的且临时值由于提升而存活到程序结束,这也是允许的, // 因为借用不在尾部位置,因此临时值的作用域 // 不会通过临时生命周期延长而扩展。 const C: () = { let _: &'static mut [u8] = &mut []; }; // 正确 // ~~ // 已提升的临时值。 }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 }; // 正确 // ~~~~~~~ // 空数组即使在可变借用之后也会被提升。 }但我们不允许这段类似的代码:
#![allow(unused)] fn main() { const C: &[u8] = &mut []; // 错误 // ~~~~~~~ // 尾部表达式。 }这两者之间的区别在于,在第一种情况下,空数组被提升但其作用域不经历临时生命周期延长,因此我们认为位置表达式是瞬态的(即使在提升之后该位置确实存活到程序结束)。在第二种情况下,空数组临时值的作用域确实经历了生命周期延长,因此由于是对生命周期延长临时值的可变借用而被拒绝(因此借用了非瞬态位置表达式)。
这种效果令人惊讶,因为在这种情况下,临时生命周期延长导致比没有它更少的代码可以编译。
更多细节请参见 issue #143129。
-
#![allow(unused)] fn main() { use core::cell::UnsafeCell; const _: u8 = unsafe { let x: *mut u8 = &raw mut *&mut 0; // ^^^^^^^ // 可变引用的解引用。 *x = 1; // 可变指针的解引用。 *(x as *const u8) // 常量指针的解引用。 }; const _: u8 = unsafe { let x = &UnsafeCell::new(0); *x.get() = 1; // 内部可变值的修改。 *x.get() }; }
- 分组表达式。
- 强制转换表达式,除了
- 指针到地址的强制转换和
- 函数指针到地址的强制转换。
- 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 函数由编译器在编译时解释。解释发生在编译目标的环境中,而不是主机环境中。因此,如果你针对 32 位系统编译,usize 是 32 位,无论你是在 64 位还是 32 位系统上构建。
当 const 函数在 const 上下文之外调用时,其行为与没有 const 限定符时相同。
const 函数的主体只能使用常量表达式。
const 函数不允许是 async。
const 函数的参数和返回类型的类型限制为与 const 上下文兼容的类型。