Skip to content

Commit 34049bb

Browse files
Merge pull request #778 from lcnr/const-generics-beta
add const generics blog post
2 parents 97f0571 + cf8c845 commit 34049bb

File tree

1 file changed

+127
-0
lines changed

1 file changed

+127
-0
lines changed
+127
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,127 @@
1+
---
2+
layout: post
3+
title: "Const generics MVP hits beta!"
4+
author: The const generics project group
5+
---
6+
7+
After more than 3 years since the [original RFC for const generics](https://github.com/rust-lang/rfcs/blob/master/text/2000-const-generics.md) was accepted, **the first version of const generics is now available in the Rust beta channel!** It will be available in the 1.51 release, which is expected to be released on **March 25th, 2021**. Const generics is one of the [most highly anticipated](https://blog.rust-lang.org/2020/12/16/rust-survey-2020.html) features coming to Rust, and we're excited for people to start taking advantage of the increased power of the language following this addition.
8+
9+
Even if you don't know what const generics are (in which case, read on!), you've likely been benefitting from them: const generics are already employed in the Rust standard library to improve the ergonomics of arrays and diagnostics; more on that below.
10+
11+
With const generics hitting beta, let's take a quick look over what's actually being stabilized, what this means practically, and what's next.
12+
13+
## What are const generics?
14+
15+
Const generics are generic arguments that range over constant values, rather than types or lifetimes. This allows, for instance, types to be parameterized by integers. In fact, there has been one example of const generic types since early on in Rust's development: the array types `[T; N]`, for some type `T` and `N: usize`. However, there has previously been no way to abstract over arrays of an arbitrary size: if you wanted to implement a trait for arrays of any size, you would have to do so manually for each possible value. For a long time, even the standard library methods for arrays were limited to arrays of length at most 32 due to this problem. This restriction was [finally lifted in Rust 1.47](https://blog.rust-lang.org/2020/10/08/Rust-1.47.html#traits-on-larger-arrays) - a change that was made possible by const generics.
16+
17+
Here's an example of a type and implementation making use of const generics: a type wrapping a pair of arrays of the same size.
18+
19+
```rust
20+
struct ArrayPair<T, const N: usize> {
21+
left: [T; N],
22+
right: [T; N],
23+
}
24+
25+
impl<T: Debug, const N: usize> Debug for ArrayPair<T, N> {
26+
// ...
27+
}
28+
```
29+
30+
### Current restrictions
31+
32+
The first iteration of const generics has been deliberately constrained: in other words, this version is the MVP (minimal viable product) for const generics. This decision is motivated both by the additional complexity of general const generics (the implementation for general const generics is not yet complete, but we feel const generics in 1.51 are already very useful), as well as by the desire to introduce a large feature gradually, to gain experience with any potential shortcomings and difficulties. We intend to lift these in future versions of Rust: see [what's next](#whats-next).
33+
34+
#### Only integral types are permitted for const generics
35+
36+
For now, the only types that may be used as the type of a const generic argument are the types of integers (i.e. signed and unsigned integers, including `isize` and `usize`) as well as `char` and `bool`. This covers a primary use case of const, namely abstracting over arrays. In the future, this restriction will be lifted to allow more complex types, such as `&str` and user-defined types.
37+
38+
#### No complex generic expressions in const arguments
39+
40+
Currently, const parameters may only be instantiated by const arguments of the following forms:
41+
42+
- A standalone const parameter.
43+
- A literal (i.e. an integer, bool, or character).
44+
- A concrete constant expression (enclosed by `{}`), involving no generic parameters.
45+
46+
For example:
47+
```rust
48+
fn foo<const N: usize>() {}
49+
50+
fn bar<T, const M: usize>() {
51+
foo::<M>(); // ok: `M` is a const parameter
52+
foo::<2021>(); // ok: `2021` is a literal
53+
foo::<{20 * 100 + 20 * 10 + 1}>(); // ok: const expression contains no generic parameters
54+
55+
foo::<{ M + 1 }>(); // error: const expression contains the generic parameter `M`
56+
foo::<{ std::mem::size_of::<T>() }>(); // error: const expression contains the generic parameter `T`
57+
58+
let _: [u8; M]; // ok: `M` is a const parameter
59+
let _: [u8; std::mem::size_of::<T>()]; // error: const expression contains the generic parameter `T`
60+
}
61+
```
62+
63+
## By-value array iterator
64+
65+
In addition to the language changes described above, we've also started adding methods to the standard library taking advantage of const generics. While most are not yet ready for stabilization in this version, there is one method that has been stabilized. [`array::IntoIter`](https://doc.rust-lang.org/nightly/std/array/struct.IntoIter.html) allows arrays to be iterated by value, rather than by reference, addressing a significant shortcoming. There is ongoing discussion about the possibility of implementing `IntoIterator` directly for arrays, though there are [backwards-compatibility concerns](https://github.com/rust-lang/rust/pull/65819) that still have to be addressed. `IntoIter::new` acts as an interim solution that makes working with arrays significantly simpler.
66+
67+
```rust
68+
use std::array;
69+
fn needs_vec(v: Vec<i32>) {
70+
// ...
71+
}
72+
73+
let arr = [vec![0, 1], vec![1, 2, 3], vec![3]];
74+
for elem in array::IntoIter::new(arr) {
75+
needs_vec(elem);
76+
}
77+
```
78+
79+
## What's next?
80+
81+
### Const generics and default arguments
82+
83+
Generic parameters must currently come in a specific order: lifetimes, types, consts. However, this causes difficulties when one attempts to use default arguments alongside const parameters. For the compiler to know which generic argument is which, any default arguments need to be placed last. These two constraints - "types come before consts", and "defaults come last" - conflict with each other for definitions that have default type arguments *and* const parameters.
84+
85+
The solution to this is to relax the ordering constraint so that const parameters may precede type arguments. However, there turn out to be subtleties involved in implementing this change, because the Rust compiler currently makes assumptions about parameter ordering that require some delicacy to remove.
86+
87+
In light of similar design questions around defaults for const arguments, these are also currently not supported in version 1.51. However, fixing the parameter ordering issues above will also unblock const defaults.
88+
89+
### Const generics for custom types
90+
91+
For a type to be valid, in theory, as the type of a const parameter, we must be able to compare values of that type at compile-time. Furthermore, equality of values should be well-behaved (namely, it should be deterministic, reflexive, symmetric, and transitive). To guarantee these properties, the concept of *structural equality* was introduced in the [const generics RFC](https://github.com/rust-lang/rfcs/blob/master/text/2000-const-generics.md): essentially this includes any type with `#[derive(PartialEq, Eq)]` whose members also satisfy structural equality.
92+
93+
There are [still some questions](https://github.com/rust-lang/rust/issues/74446) concerning precisely how structural equality should behave, and [prerequisites for implementation](https://github.com/rust-lang/compiler-team/issues/323). Primitive types are significantly simpler, which has allowed use to stabilize const generics for these types before more general types.
94+
95+
### Const generics with complex expressions
96+
97+
There are several complexities involved in supporting complex expressions. A feature flag, `feature(const_evaluatable_checked)`, is available in the Nightly channel, which enables a version of complex expression support for const generics.
98+
99+
One difficulty lies in the necessity of having some way to compare unevaluated constants, as the compiler does not automatically know that two syntactically identical expressions are actually equal. This involves a kind of symbolic reasoning about expressions, which is a complex problem in general.
100+
```rust
101+
// The two expressions `N + 1` and `N + 1` are distinct
102+
// entities in the compiler, so we need a way to check
103+
// if they should be considered equal.
104+
fn foo<const N: usize>() -> [u8; N + 1] {
105+
[u8; N + 1]
106+
}
107+
```
108+
109+
We also want a way to deal with potential errors when evaluating generic operations.
110+
```rust
111+
fn split_first<T, const N: usize>(arr: [T; N]) -> (T, [T; N - 1]) {
112+
// ...
113+
}
114+
115+
fn generic_function<const M: usize>(arr: [i32; M]) {
116+
// ...
117+
let (head, tail) = split_first(arr);
118+
// ...
119+
}
120+
```
121+
Without a way to restrict the possible values of `M` here, calling `generic_function::<0>()` would cause an error when evaluating `0 - 1` that is not caught at declaration time and so may unexpectedly fail for downstream users.
122+
123+
There are [design questions](https://github.com/rust-lang/rust/issues/68436) about how exactly to express these kinds of bounds, which need to be addressed before stabilising complex const arguments.
124+
125+
## Summary
126+
127+
With such a major new feature, there are likely to be a few rough edges. If you encounter any problems, even if it's as minor as a confusing error message, [please open an issue](https://github.com/rust-lang/rust/issues/new/choose)! We want the user experience to be the best it can possibly be - and any issues now are likely to be even more important for the next iterations of const generics.

0 commit comments

Comments
 (0)