Reputation: 115
I want to use a function that concatenates two arrays with a declaration like so:
fn concatenate<const COUNT1: usize, const COUNT2: usize>(a: [i32;COUNT1], b: [i32;COUNT2]) -> [i32;{COUNT1+COUNT2}];
The problem is the return type. Here is the specific error:
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`
This function seems very easy to monomorphize and I don't understand why the compiler doesn't allow it. The rust book only states(twice) it is not allowed, but doesn't explain why:
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.
Does anyone know how this pattern is against the rust model, because at least from my point of view it definitely isn't an implementation limitation. Here's the whole function if it will help:
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
}
Upvotes: 8
Views: 1961
Reputation: 71410
@Netwave focused on what to do, I want to explain why.
There are two problems with this: implementation problems and design problems.
The implementation problems are various (lots of) bugs with the compilation of such generics that cause ICEs (Internal Compiler Errors, a crash of the compiler) and I think even miscompilations and soundness issues.
The design problem is more problematic: this expression is not as trivial as it seems. What if it overflows?
In a case a constant expression overflows we display an error. Overflow is just one consideration: there are many reasons that can cause an expression to fail to compile (for example, array too big, or access out of bounds). It is easy to reject them when the constant is not generic; but doing so with a generic constant is much harder. We have two choices:
The first is to be like C++. That is, allow that to compile and error if it is actually gets happen, like C++ does with its templates.
The problem with that is that Rust chose deliberately to not be like C++ in the general case, and went with the approach of trait bounds, that is, require the code to be able to compile as generic, instead of requiring it to compile when monomorphized. And this is for very good reasons: post-monomorphization errors are really bad (like C++), and type-checking this is expensive - cargo check
does not bail on post-monomorhpization errors, only cargo build
. That can cause cargo build
to fail why cargo check
succeeds, and that is really bad. We already have some post-monomorphization errors, and indeed this is what happens to them:
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>>();
}
The second approach, which is what generic_const_exprs
uses today, is to require every expression that may fail to be repeated in the signature. We earn two things by doing that:
Problem is, requiring to repeat every expression in the signature is, ah, repetitive. And not obvious. So we are still seeking for ideas (here is one). And we cannot stabilize generic_const_exprs
until both the design issues and the implementation issues are settled.
Upvotes: 12
Reputation: 42766
You need to enable the feature generic_const_exprs
, which is only available in nightly for the moment (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
}
Upvotes: 7