# `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.