为什么 Rust 只允许数组大小的独立常量?

Why does rust only allow standalone constant for array size?

我想使用一个连接两个数组的函数,声明如下:

fn concatenate<const COUNT1: usize, const COUNT2: usize>(a: [i32;COUNT1], b: [i32;COUNT2]) -> [i32;{COUNT1+COUNT2}];

问题是 return 类型。这是具体的错误:

error: generic parameters may not be used in const operations
 --> src\main.rs:4:101
  |
4 | fn concatenate<const COUNT1: usize, const COUNT2: usize>(a: [i32;COUNT1], b: [i32;COUNT2]) -> [i32;{COUNT1+COUNT2}] {
  |                                                                                                     ^^^^^^ cannot perform const operation using `COUNT1`
  |
  = help: const parameters may only be used as standalone arguments, i.e. `COUNT1`

这个函数看起来很容易单态化,我不明白为什么编译器不允许。 rust book 只声明(两次)不允许,但没有解释原因:

Const parameters can be used anywhere a const item can be used, with the exception that when used in a type or array repeat expression, it must be standalone (as described below).

As a further restriction, const parameters may only appear as a standalone argument inside of a type or array repeat expression.

有谁知道这种模式与 rust 模型有何不同,因为至少从我的角度来看,这绝对不是实现限制。如果对您有帮助,这是完整的功能:

fn concatenate<const COUNT1: usize, const COUNT2: usize>(a: [i32;COUNT1], b: [i32;COUNT2]) -> [i32;{COUNT1+COUNT2}] {
    let mut output = [0i32;{COUNT1+COUNT2}];
    output.copy_from_slice(
        &a.iter().chain(b.iter()).map(|&item| item).collect::<Vec<i32>>()
    );
    output
}

您需要启用 generic_const_exprs 功能,该功能目前仅适用于 nightly (rust 1.63):

#![feature(generic_const_exprs)]

fn concatenate<const COUNT1: usize, const COUNT2: usize>(a: [i32;COUNT1], b: [i32;COUNT2]) -> [i32;{COUNT1+COUNT2}] {
    let mut output = [0i32;{COUNT1+COUNT2}];
    output.copy_from_slice(
        &a.iter().chain(b.iter()).map(|&item| item).collect::<Vec<i32>>()
    );
    output
}

Playground

@Netwave 专注于做什么,我想解释一下为什么

这有两个问题:实施问题和设计问题。

实现问题是编译此类泛型导致 ICE(内部编译器错误,编译器崩溃)的各种(很多)错误,我认为甚至是编译错误和健全性问题。

设计问题比较有问题:这个表达式并不像看起来那么琐碎。溢出了怎么办?

在常量表达式溢出的情况下我们display an error。溢出只是一个考虑因素:导致表达式编译失败的原因有很多(例如,数组太大,或越界访问)。当常量不是泛型时,很容易拒绝它们;但是使用通用常量这样做要困难得多。我们有两个选择:

首先是要像C++。也就是说,允许它编译并出错,如果它实际上得到发生,就像 C++ 对其模板所做的那样。

问题是 Rust 在一般情况下故意选择 而不是 像 C++,并采用特征边界的方法,即要求代码能够将 编译为通用 ,而不是要求它在单态化 时编译 。这是有充分理由的:post-monomorphization 错误真的很糟糕(像 C++),而且 type-checking 这很昂贵 - cargo check 不会因 post-monomorhpization 错误而保释,只有 cargo build。这可能导致 cargo build 失败,为什么 cargo check 成功,那就是 非常糟糕 。我们已经有一些 post-monomorphization 错误,而这确实发生在他们身上:

trait Trait {
    const MAY_FAIL: u8;
}
struct S<const BASE: u8>;
impl<const BASE: u8> Trait for S<BASE> {
    const MAY_FAIL: u8 = BASE + 1;
}

fn cause_post_mono_error<T: Trait>() {
    _ = T::MAY_FAIL;
}

fn main() {
    // This will pass `cargo check`, but fail `cargo build`!
    cause_post_mono_error::<S<255>>();
}

generic_const_exprs今天使用的第二种方法是要求签名中每个可能无法重复的表达式。我们通过这样做赚取了两件事:

  1. 我们只能检查签名,因此在调用站点我们知道这个表达式会失败——它不满足要求。
  2. 向函数体添加一个可能会失败的表达式,这是一个破坏性的变化,需要反映在签名上——这很好,因为 Rust 的哲学是 每个破坏性变化 必须反映在签名上。这可以防止像在 C++ 中那样的危险,即使使用广泛的测试套件,您也永远无法确定某些更改不会对某些客户造成破坏。

问题是,要求重复签名中的每个表达式,啊,重复。而且不明显。所以我们还在征求意见(here is one)。在 设计问题 实施问题都得到解决之前,我们无法稳定 generic_const_exprs