# `min_generic_const_exprs` written anonymously by two random people you will never guess who ## What do we want to avoid ### unused substs see https://github.com/rust-lang/project-const-generics/blob/master/design-docs/anon-const-substs.md#unused-substs Closures with anon consts in args cause "closure/generation that references itself" errors https://github.com/rust-lang/rust/issues/85665 ```rust #![feature(generic_const_exprs)] struct Foo<F: Fn([(); N + 1]), const N: usize>(F); fn bar() { Foo::<_, 2>(|_|{}); } ``` AnonConsts mark all params as invariant ```rust #![feature(generic_const_exprs)] struct Foo<T, U, const N: usize>([(); N + 1]) where [(); N + 1]:; ``` ### cycle errors from `ConstEquate` calling typeck anon consts can refer to themselves via their own `where`-bounds see https://github.com/rust-lang/project-const-generics/blob/master/design-docs/anon-const-substs.md#consts-in-where-bounds-can-reference-themselves additionally by requiring each const to be typeck'd to build thir in order to unify consts we can trivially cause cycles if typechecking a const requires solving a `ConstEquate` obligation ### cycle error from using `Self` in trait where clause the following currently cycles and should be handled somehow: ```rust trait Trait where evaluatable { <Self as Trait>::CONST } { const CONST: usize; } ``` ### unification of anon consts is fragile af e.g. https://github.com/rust-lang/rust/pull/90529 by using thir to unify consts we run the risk of accidentally exposing impl details of how we lower language things to thir which is concerning ## Potential Solution Introduce a `feature(min_generic_const_exprs)` that only allows assoc and free constants as anon consts: ```rust // requires generic free constants and generic associated constants // // GFCs and GACs // // I do want these anyways, so :shrug: const ADD_ONE<const N: usize> = N + 1; fn push<T, const N: usize>(a: [T; N], v: T) -> [T; ADD_ONE::<N>] { // ... } ``` or using an associated constant ```rust trait AddOne<const N: usize> { const ADDED: usize; } impl<const N: usize> AddOne<N> for () { const ADDED: usize = N + 1; } fn push<T, const N: usize>(a: [T; N], v: T) -> [T; <() as AddOne<N>>::ADDED] { // ... } ``` ```rust fn push<T, const N: usize>(a: [T; N], v: T) -> [T; N + 1] { //~^ error: `N + 1` is not a free const or an assoc const } ``` The following also compiles: ```rust const ADD<const LHS: usize, const RHS: usize> = LHS + RHS; fn foo<const N: usize>() -> [(); ADD<N, 3>] { [(); ADD<N, { 1 + 2 }>] } ``` #### Advantages - no unused substs, all are explicit - solves almost all `where`-bound cycles as we do not require `typeck` to unify consts - unification is vastly simpler and has no forwards compatibility issues nor does it rely on typeck - for assoc consts we just check that the trait refs are the same - for free consts we just check that the items are the same and substs unify - izi to implement :3 #### Disadvantages - `trait Trait where evaluatable { <Self as Trait>::CONST }` still cycles - fairly restrictive (i.e. `N + 1` is not allowed) but this will be solved by `feature(generic_const_exprs)` ### Ways to "safely" extend this (not too relevant) ```rust fn push<T, const N: usize>(a: [T; N], v: T) -> [T; const<N> { N + 1 }] { // ... } ``` `const<N> where x { expr }` has completely separate generics from its parent. We can still keep `feature(generic_const_exprs)` to allow anon const expressions which inherit their parents generics like `N + 1`. #### Advantages - Less annoying to use than `ADD_ONE::<N>` - Allows crates to be more composable as the ecosystem does not need one crate providing an `ADD_ONE` const - By requiring generic consts to be marked with `const<GENERICS> WHERE_CLAUSES` we can avoid any `unused_substs` issues #### Disadvantages - syntax bikeshed ![bikeshed wrt syntax](https://tse3.mm.bing.net/th?id=OIP.I7ph6KAiuYMu0U_3UZHJFwHaHa&pid=Api) - these will be substantially different from ordinary inline consts as they require explicit mention of the used params and where bounds - HOW TO UNIFY?!?! ### `feature(min_generic_const_exprs)` language future compat by keeping the special case for anon consts which only consist of named constants, or by correctly dealing with both unused substs and allowing the evaluation of subtrees of `AbstractConst`s, `feature(generic_const_exprs)` should not introduce any breaking changes after `feature(min_generic_const_exprs)` has been stabilized. ### `feature(min_generic_const_exprs)` library concerns **Issue**: By requiring the use of named constants which only unify with themselves, constants for the same expr, e.g. `N + 1`, which are defined in separate libraries, do not unify. **Solution**: Add a module to the std called `std::constants` and add constants for pretty much all common ops, e.g. `std::constants::ADD`. Add lints when redefining these constants. **Issue**: If we later stabilize `feature(generic_const_exprs)` we need `ADD<N, 1>` to unify with `N + 1`. Otherwise we wouldn't be able to update the std api. **Solution**: Add an attribute, e.g. `#[transparent_const]`, which, when applied to named constants, causes rustc to "unwrap" that constant when used in types. With this `#[transparent_const] const ADD<const N: usize, const M: usize>: usize = N + M` unifies with addition. **Issue**: Unifying associated constants with other constants, consider the following example ```rust trait ReturnsArray { const RETURN_LENGTH<const N: usize>; fn foo<const N: usize>(arr: [u8; N]) -> [u8; RETURN_LENGTH::<N>]; } impl ReturnsArray for () { const RETURN_LENGTH<const N: usize> = ADD<N, 1>; fn foo<const N: usize>(arr: [u8; N]) -> [u8; RETURN_LENGTH::<N>] { // `ADD<N, 1>` and `RETURN_LENGTH::<N>` don't trivially unify. // so this would error. arr.push(0); } } ``` **Solution** we probably again need something like`#[transparent_const]` or even do this implicitly if the definition of an associated constant is another associated consts or sth.