TLDR; 在最新的 nightly 版本中,`if` 和 `match` 现在可以在常量中使用。
因此,你现在可以编写如下代码,并在编译时执行它
static PLATFORM: &str = if cfg!(unix) {
"unix"
} else if cfg!(windows) {
"windows"
} else {
"other"
};
const _: () = assert!(std::mem::size_of::<usize>() == 8, "Only 64-bit platforms are supported");
`if` 和 `match` 也可以在 `const fn` 的函数体中使用
const fn gcd(a: u32, b: u32) -> u32 {
match (a, b) {
(x, 0) | (0, x) => x,
(x, y) if x % 2 == 0 && y % 2 == 0 => 2*gcd(x/2, y/2),
(x, y) | (y, x) if x % 2 == 0 => gcd(x/2, y),
(x, y) if x < y => gcd((y-x)/2, x),
(x, y) => gcd((x-y)/2, y),
}
}
这里到底发生了什么?
以下表达式:
match
- `if` 和 `if let`
- `&&` 和 `||`
现在可以出现在以下任何上下文中:
- `const fn` 函数体
- `const` 和关联的 `const` 初始化器
- `static` 和 `static mut` 初始化器
- 数组初始化器
- 常量泛型(实验性)
如果你的 crate 启用了 `#![feature(const_if_match)]` 特性。
你可能已经注意到,短路逻辑运算符 `&&` 和 `||` 已经在 `const` 或 `static` 中是合法的了。这是通过将它们转换为非短路等价形式,即 `&` 和 `|` 来实现的。启用此特性门将关闭此 hack,并使 `&&` 和 `||` 的行为符合预期。
作为这些更改的副作用,如果同时启用了 `#![feature(const_panic)]`,则 `assert` 和 `debug_assert` 宏可以在常量上下文中使用。但是,其他断言宏(例如,`assert_eq`,`debug_assert_ne`)仍然被禁止,因为它们需要在其参数上调用 `Debug::fmt`。
循环结构 `while`,`for` 和 `loop` 也被禁止,并将单独设置特性门。正如你在上面看到的,可以使用递归来模拟循环作为临时措施。但是,非递归版本通常更有效率,因为据我所知,Rust 不进行尾调用优化。
最后,`?` 运算符在常量上下文中仍然被禁止,因为它的解糖包含对 `From::from` 的调用。`const` trait 方法的设计仍在讨论中,并且 `?` 和 `for`(它会被解糖为调用 `IntoIterator::into_iter`)都不能使用,直到最终做出决定。
接下来是什么?
此更改将允许将大量标准库函数设为 `const`。你可以帮助完成此过程!要开始,这里有一个 可以轻松地被 const 化 的数字函数列表。转换为 `const fn` 需要两个步骤。首先,将 `const` 添加到函数定义中,并添加 `#[rustc_const_unstable]` 属性。这允许 nightly 用户在常量上下文中调用它。然后,经过一段时间的实验,该属性将被删除,并且该函数的 const 性质将稳定。请参阅 #61635 以获取第一步的示例,并参阅 #64028 以获取第二步的示例。
就我个人而言,我期待这个特性已经很久了,我迫不及待地想开始使用它。如果你有同感,我将非常感谢你测试此功能的极限!尝试将 `Cell` 和带有 `Drop` impl 的类型偷偷放入它们不应该存在的地方,用实现不佳的递归函数炸毁堆栈(请参阅上面的 `gcd`),如果出现严重错误,请告知我们。
为什么花了这么长时间?
Miri 引擎(rust 在底层用于编译时函数评估)已经能够做到这一点了。但是,rust 需要静态保证有关 `const` 中变量的某些属性,例如它们是否允许内部可变性或它们是否具有需要调用的 `Drop` 实现。例如,我们必须拒绝以下代码,因为它会导致 `const` 在运行时是可变的!
const CELL: &std::cell::Cell<i32> = &std::cell::Cell::new(42); // Not allowed...
fn main() {
CELL.set(0);
println!("{}", CELL.get()); // otherwise this could print `0`!!!
}
但是,只要我们可以证明该类型的实际 *值* 不是,`const` 有时可以包含对可能具有内部可变性的 *类型* 的引用。这对于具有 “unit variant” 的 `enum` 非常有用(例如,`Option::None`)。
const NO_CELL: Option<&std::cell::Cell<i32>> = None; // OK
有关 const 上下文中的 `Drop` 和 内部可变性规则的更详细(但非规范性)的解释可以在 `const-eval` 存储库中找到。
当涉及到循环和条件等复杂的控制流时,保证变量值的属性并非易事。实现此功能需要扩展 rust 中现有的数据流框架,以便我们可以正确跟踪每个局部变量在控制流图中的值。目前,分析非常保守,尤其是在值在复合数据类型中移入和移出时。例如,即使启用了特性门,以下代码也无法编译。
const fn imprecise() -> Vec<i32> {
let tuple: (Vec<i32>,) = (Vec::new(),);
tuple.0
}
即使 `Vec::new` 创建的 `Vec` 永远不会在 `const fn` 内部被 drop,我们也没有检测到 `tuple` 的所有字段都已移出,因此保守地假设 `tuple` 的 drop impl 将会运行。虽然这个特殊的例子很简单,但还有其他更复杂的例子需要更全面的解决方案。我们希望在这里有多精确是一个悬而未决的问题,因为更精确意味着更长的编译时间,即使对于那些不需要更高表现力的用户来说也是如此。