const generics update

Hey

Image Not Showing Possible Reasons
  • The image file may be corrupted
  • The server hosting the image is unavailable
  • The image path is incorrect
  • The image format is not supported
Learn More →
after more than half a year since the last update there are some news wrt const generics. #88369 removes the unstable features const_generics, lazy_normalization_consts, and const_evaluatable_checked. Depending on what you need, you can now choose between feature(const_param_types) and feature(generic_const_exprs).

While I am at it, here's a quick overview of the new - and preexisting - features and how much still needs to be done for them to get stabilized:

feature(const_param_types)

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.

#![feature(const_param_types)]

#[derive(PartialEq, Eq)]
enum ImageFormat {
    Rgb8,
    Rgba8,
    // ...c
}

struct Image<const FORMAT: ImageFormat> {
    // ...
}

impl Image<{ ImageFormat::Rgba }> {
    fn alpha(&self, pixel: PixelLocation) -> u8 {
        // ...
    }
}

Note that even with this feature generic const parameter types, such as struct Foo<T, const N: T> { ... } are forbidden.
While allowing such things is desired, it adds additional complications exceeding our current capacity.

There are still two major blockers here, the first being the transition to valtrees (#83234). Using valtrees we're able
to easily deal with more complex values than integers as const arguments.

Additionally, we have to figure out which types we even want to allow as const parameter types. This ties into the discussion
about "structural match", which is still an ongoing.

While the issues mentioned above are definitely not trivial,
I wouldn't be surprised to see this ready for stabilization in a few months.

feature(generic_const_exprs)

Without any unstable feature, const arguments must either be a fully concrete expression or a generic parameters by themselves, so things like N + 1 are forbidden. With this feature, expressions using generic parameters are possible.

#![feature(generic_const_exprs)]

fn split_first<T, const N: usize>(arr: [T; N]) -> (T, [T; N - 1]) {
    // ...
}

struct BitSet<const SIZE: usize>
where
    [u8; (SIZE + 7) / 8]: Sized,
{
    storage: [u8; (SIZE + 7) / 8],
}

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. If the constant expression expr would otherwise not be used in the where bounds or function signature, we tend to add a [u8; expr]: Sized bound to the where-clauses of our item. While it is highly likely that we will add a dedicated syntax for these bounds in the future, we're waiting with this until the rest of this feature is more mature.

This feature is still from being stable and has some major unsolved issues. Especially for constants inside of where-bounds there are a lot of subtle bugs and backwards incompatabilities we have to fix before we can even think about how to stabilize this.

feature(const_generics_defaults)

Similar to type parameter defaults, this feature adds the ability to declare default values for const parameters.

#![feature(const_generics_defaults)]

struct ArrayStorage<T, const N: usize = 2> {
    arr: [T; N],
}

impl<T> ArrayStorage<T> {
    fn new(a: T, b: T) -> ArrayStorage<T> {
        ArrayStorage {
            arr: [a, b],
        }
    }
}

To allow type parameter defaults in the same listing as const parameters we also intend to remove the ordering restriction for
type and const parameters, allowing struct Foo<const N: usize, T = [u32; N]> { ... }.

This feature is pretty much ready for stabilization and is currently blocked on figuring out any potential edge cases for the
stabilization report.

feature(generic_arg_infer)

While it is already possible to use a wildcard _ for type arguments inside of bodies, this is not the case for const arguments.
This feature adds this capability for constants.

#![feature(generic_arg_infer)] fn array_from<T, U, const N: usize>(arr: [T; N]) -> [U; N] where U: From<T>, { arr.map(From::from) } fn main() { let x = ["this", "is", "a", "six", "element", "array"]; // using `_` for the parameter `N` lets // the compiler infer the correct value let _y = array_from::<_, String, _>(x); }

This feature is not yet ready for stabilization, though to my knowledge there aren't any big blockers here.
To confidently stabilize this we are probably in need of some large refactorings though, as the current setup
feels fairly fragile in some areas.