|
| 1 | +--- |
| 2 | +layout: post |
| 3 | +title: "Splitting the const generics features" |
| 4 | +author: lcnr |
| 5 | +description: "Splitting the const generics features" |
| 6 | +team: The Const Generics Project Group <https://rust-lang.github.io/project-const-generics/> |
| 7 | +--- |
| 8 | + |
| 9 | +After the stabilization of the const generics MVP in version 1.51, the const generics project group has continued to |
| 10 | +work on const generics. Large parts of this work were gated behind the feature gates `const_generics` and `const_evaluatable_checked`. As time went on, the |
| 11 | +`const_generics` feature became fairly useless on its own while the name of |
| 12 | +`const_evaluatable_checked` didn't really capture what this feature was intended to do. |
| 13 | + |
| 14 | +To improve this, we have recently removed the features `const_generics`, `lazy_normalization_consts`, and `const_evaluatable_checked`. They have been replaced by `feature(adt_const_params)` and `feature(generic_const_exprs)`. |
| 15 | + |
| 16 | +As there is a lot going on with const generics, here's a quick overview of the new - and preexisting - features and how much still needs to be done for them to get stabilized: |
| 17 | + |
| 18 | +### `feature(adt_const_params)` |
| 19 | + |
| 20 | +On stable, only integers, `char` and `bool` are allowed as the types of const parameters. This feature allows additional types, such as `&'static str` and user defined types. |
| 21 | +```rust |
| 22 | +#![feature(adt_const_params)] |
| 23 | + |
| 24 | +#[derive(PartialEq, Eq)] |
| 25 | +enum ImageFormat { |
| 26 | + Rgb8, |
| 27 | + Rgba8, |
| 28 | + // ...c |
| 29 | +} |
| 30 | + |
| 31 | +struct Image<const FORMAT: ImageFormat> { |
| 32 | + // ... |
| 33 | +} |
| 34 | + |
| 35 | +impl Image<{ ImageFormat::Rgba }> { |
| 36 | + fn alpha(&self, pixel: PixelLocation) -> u8 { |
| 37 | + // ... |
| 38 | + } |
| 39 | +} |
| 40 | +``` |
| 41 | +Note that even with this feature, generic const parameter types, such as `struct Foo<T, const N: T> { ... }`, are forbidden. |
| 42 | +While allowing such things is desired, it adds additional complications exceeding our current capacity. |
| 43 | + |
| 44 | +There are still two major blockers for stabilization: |
| 45 | + |
| 46 | +The first being the [transition to valtrees](https://github.com/rust-lang/rust/pull/83234). Valtrees are a representation of values as trees with integer nodes, simplifiying the way we interact with more complex types. |
| 47 | + |
| 48 | +Additionally, we have to figure out which types we *even want* to allow as const parameter types. This ties into the discussion |
| 49 | +about ["structural match"](https://github.com/rust-lang/rust/issues/74446), which is still ongoing. |
| 50 | + |
| 51 | +While the issues mentioned above are definitely not trivial, it is definitely possible for this to be ready for stabilization in a few months. |
| 52 | + |
| 53 | +### `feature(generic_const_exprs)` |
| 54 | + |
| 55 | +Without any unstable features, const arguments must either be a fully concrete expression or a generic parameter by itself, so constants like `N + 1` are forbidden. With this feature, expressions using generic parameters are possible. |
| 56 | + |
| 57 | +```rust |
| 58 | +#![feature(generic_const_exprs)] |
| 59 | + |
| 60 | +fn split_first<T, const N: usize>(arr: [T; N]) -> (T, [T; N - 1]) { |
| 61 | + // ... |
| 62 | +} |
| 63 | + |
| 64 | +struct BitSet<const SIZE: usize> |
| 65 | +where |
| 66 | + [u8; (SIZE + 7) / 8]: Sized, |
| 67 | +{ |
| 68 | + storage: [u8; (SIZE + 7) / 8], |
| 69 | +} |
| 70 | +``` |
| 71 | + |
| 72 | +We currently require the user to add bounds asserting that generic constants evaluate successfully. For all constants visible in the API of an item, these bounds are added implicitly. |
| 73 | + |
| 74 | +If the constant expression `expr` of type `Foo` would otherwise not be used in the `where`-clauses or function signature, we add an otherwise irrelevant bound mentioning `expr` to the `where`-clauses of our item. For this one can define a `struct Evaluatable<const N: Foo>;` and use `Evaluatable<{ expr }>:` as a bound. If `expr` is of type `usize` we tend to use `[u8; expr]:` |
| 75 | +or `[u8; expr]: Sized` for this. While it is highly likely that we will add a dedicated syntax for these bounds in the future, we are waiting with this until the rest of this feature is more mature. |
| 76 | + |
| 77 | +This feature is still far from being stable and has some [**major** unsolved issues](https://github.com/rust-lang/project-const-generics/blob/master/design-docs/anon-const-substs.md). Especially for constants inside of `where`-bounds there are a lot of subtle bugs and backwards incompatibilities we have to fix before we can even think about how to stabilize this. |
| 78 | + |
| 79 | +### `feature(const_generics_defaults)` |
| 80 | + |
| 81 | +Similar to type parameter defaults, this feature adds the ability to declare default values for const parameters. |
| 82 | + |
| 83 | +```rust |
| 84 | +#![feature(const_generics_defaults)] |
| 85 | + |
| 86 | +struct ArrayStorage<T, const N: usize = 2> { |
| 87 | + arr: [T; N], |
| 88 | +} |
| 89 | + |
| 90 | +impl<T> ArrayStorage<T> { |
| 91 | + fn new(a: T, b: T) -> ArrayStorage<T> { |
| 92 | + ArrayStorage { |
| 93 | + arr: [a, b], |
| 94 | + } |
| 95 | + } |
| 96 | +} |
| 97 | +``` |
| 98 | +To allow type parameter defaults in the same listing as const parameters we also intend to remove the ordering restriction for |
| 99 | +type and const parameters, allowing `struct Foo<const N: usize, T = [u32; N]> { ... }`. |
| 100 | + |
| 101 | +This feature is pretty much ready for stabilization and is currently blocked on figuring out any potential edge cases for the |
| 102 | +stabilization report. |
| 103 | + |
| 104 | +### `feature(generic_arg_infer)` |
| 105 | + |
| 106 | +While it is already possible to use a wildcard `_` for type arguments inside of bodies, this is not the case for const arguments. |
| 107 | +This feature adds this capability for constants. |
| 108 | + |
| 109 | +```rust |
| 110 | +#![feature(generic_arg_infer)] |
| 111 | +fn array_from<T, U, const N: usize>(arr: [T; N]) -> [U; N] |
| 112 | +where |
| 113 | + U: From<T>, |
| 114 | +{ |
| 115 | + arr.map(From::from) |
| 116 | +} |
| 117 | + |
| 118 | +fn main() { |
| 119 | + let x = ["this", "is", "a", "six", "element", "array"]; |
| 120 | + // using `_` for the parameter `N` lets |
| 121 | + // the compiler infer the correct value |
| 122 | + let _y = array_from::<_, String, _>(x); |
| 123 | +} |
| 124 | +``` |
| 125 | + |
| 126 | +This feature is not yet ready for stabilization, though there aren't any known big blockers here. |
| 127 | +To confidently stabilize this we are probably in need of some large refactorings though, as the current setup |
| 128 | +feels fairly fragile in some areas. |
| 129 | + |
0 commit comments