Try   HackMD

const-generics-defaults stabilisation report

stabilisation PR

Stabilization report

This is the stabilization report for const parameter defaults and the removal of the type and const parameter ordering restriction. Its feature is #![feature(const_generics_defaults)].

This report is a collaborative effort of @BoxyUwU and @lcnr.

Summary

While supplying default arguments for type parameters is already possible on stable, this is not allowed for const parameters. Due to the restriction that type parameters must be in front of all const parameters, type parameter defaults cannot be used on stable if a const parameter exists.

This stabilization allows type and const parameters to be in an arbitrary order and adds the ability to specify default values for const parameters in all places where this is already allowed for type parameters. These defaults have the same restriction as other const arguments, so they must either be a fully concrete expression or another const parameter.

Motivation

Const parameter defaults

  • Reduces the difference between const generics and type generics by allowing both to have defaults
  • Allows libraries to backwards compatibly add const generics to traits and types
  • Makes working with const generics more ergonomic when there is a reasonable default for the generic parameter

Removal of the ordering restriction

  • Type and const parameters are similar enough and there are cases where a specific order of generic parameters is prefered for clarity.
  • With this restriction, it is not possible to have generics with both a type default (T = 32) and a const generic (const N: u8) without removing the ordering restriction

Examples

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],
        }
    }
}


struct Image<
    const WIDTH: usize,
    const HEIGHT: usize,
    // Without losing the ordering restriction,
    // we could not add a default for this type parameter.
    FORMAT: ImageFormat = PngFormat, 
> {
    // ...
}

// This order is a lot clearer than
//
//    T, U, V, F, const N: usize, const M: usize
fn cartesian_product<
    T, const N: usize,
    U, const M: usize,
    V, F
>(a: [T; N], b: [U; M]) -> [[V; N]; M]
where
    F: FnMut(&T, &U) -> V
{
    // ...    
}

Detailed feature description

Ordering

The ordering restriction between types and consts is removed. The ordering of generic params is now: lifetimes, then type and const parameters.

We previously restricted the ordering due to implementation concerns, which have since been fixed.

Example

struct Foo<
    T, 
    const N: usize, 
    U, 
    const M: usize = 3
    V = [i64; M],
>(T, U, V);

fn foo<const N: usize, T, const M: usize>() { }

Default const parameters

It is currently possible to provide a default type to type parameters in type and trait definition. With this stabilization it becomes possible to provide default values for const parameters as well.

The used syntax is const PARAM_NAME: Ty = expr where expr is either a single segment path, a literal, or surrounded by braces, e.g. { foo() }. This restriction is identical to the restriction of const parameters and simplifies parsing.

Const defaults, for now, are not permitted to involve computations depending on generic parameters. This means that defaults may only be:

  1. const expressions that do not depend on any generic parameters, e.g. { foo() + 1 }, where foo is a const fn.
  2. standalone const parameters, e.g. N. Note that both type and const parameter defaults may only refer to preceeding parameters, meaning that struct Foo<const N: usize = M, const M: usize = 3> causes an error.

When using an expression which does not evaluate successfully, e.g. usize::MAX + 1, as a const parameter default, we eagerly emit an error. This also mirrors the behavior of type parameter defaults.

Example

struct Foo<const N: usize = 10>;
struct Bar<const N: usize = { usize::MAX - 1 }, const M: usize = N>;

fn foo() -> (Foo, Bar<10>) {
    (Foo::<10>, Bar)
}

struct Baz<const N: usize, const M: usize = { N + 1 }>;
//^ error: generic parameters may not be used in const operations

trait Trait<const N: usize> { const ASSOC: usize; }
pub struct UwU<const N: usize = { <()>::ASSOC }> where (): Trait<N>;
//^ error: no associated item named `ASSOC` found for unit type `()` in the current scope

Documentation

The Book: Does not contain any documentation about const generics
The Reference: reference#1098

Important Tests

Future Incompatibilty concerns

trait Trait<T> {
    const ASSOC: usize;
}

impl Trait<()> for usize {
    const ASSOC: usize = 2;
}

struct Foo<T, U, const N: usize = { usize::ASSOC }>(T, U)
where
    usize: Trait<T> + Trait<U>;

usize::ASSOC is resolved today, but will be ambiguous if we start providing where clauses to type level constants. This is not a new issue however, as the following code has the same issue and currently compiles on stable:

fn foo<T, U>() -> [(); { usize::ASSOC }] 
where
    usize: Trait<T> + Trait<U> {
  ...
}

struct Foo<T, U>(T, U, [(); { usize::ASSOC }])
where
    usize: Trait<T> + Trait<U>;

We therefore believe that this issue should not block the stabilization of const parameter defaults.