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



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

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

问题是返回类型。下面是具体的错误:

error: generic parameters may not be used in const operations
--> srcmain.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手册只声明(两次)不允许这样做,但没有解释原因:

Const形参可以在任何可以使用Const项的地方使用,除非在类型或数组重复表达式中使用时,它必须是独立的(如下所述)。

作为进一步的限制,const形参只能作为类型或数组重复表达式中的独立实参出现。

有人知道这个模式是如何与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
}

@ nettwave专注于做什么,我想解释为什么

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

实现问题是各种各样的(很多)错误,这些泛型的编译会导致ice(内部编译器错误,编译器崩溃),我认为甚至是错误编译和可靠性问题。

设计问题更成问题:这个表达式并不像看起来那么微不足道。如果溢出怎么办?

如果常量表达式溢出,则显示错误。溢出只是一个考虑因素:有很多原因可能导致表达式无法编译(例如,数组太大,或访问越界)。当常量不是泛型时,很容易拒绝它们;但是用一个泛型常量做这件事要困难得多。我们有两个选择:

第一个是像c++一样。也就是说,允许编译并在实际得到发生时出错,就像c++对其模板所做的那样。

的问题是Rust故意选择不像一般情况下的c++那样,并采用trait边界的方法,也就是说,要求代码能够将编译为泛型,而不是要求它在单态时编译。这是有很好的理由的:后单态错误非常糟糕(像c++),并且类型检查非常昂贵——cargo check不会对后单态错误进行检查,只有cargo build才会。导致cargo check成功而cargo build失败,这就是真的不好。我们已经有了一些后单态化错误,下面就是它们的情况:

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++那样的危险,在c++中,即使有一个广泛的测试套件,你也永远无法确保一些更改不会对某些客户造成破坏。

问题是,要求重复签名中的每个表达式,啊,是重复的。而且不明显。因此,我们仍在寻求想法(这里有一个)。在设计问题实现问题解决之前,我们无法稳定generic_const_exprs

您需要启用generic_const_exprs特性,该特性目前仅在夜间可用(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
}

游乐场

最新更新